10
votes

I am having an issue working with lifetime parameters for structs. I am not 100% sure how to describe the problem, but I created a trivial case that shows my compile time error.

struct Ref;

struct Container<'a> {
  r : &'a Ref
}

struct ContainerB<'a> {
  c : Container<'a>
}

trait ToC {
  fn to_c<'a>(&self, r : &'a Ref) -> Container<'a>;
}

impl<'a> ToC for ContainerB<'a> {
  fn to_c(&self, r : &'a Ref) -> Container<'a> {
    self.c
  }
}

The error I am getting with this is

test.rs:16:3: 18:4 error: method `to_c` has an incompatible type for trait: expected concrete lifetime, but found bound lifetime parameter 'a
test.rs:16   fn to_c(&self, r : &'a Ref) -> Container<'a> {
test.rs:17     self.c
test.rs:18   }
test.rs:16:48: 18:4 note: expected concrete lifetime is the lifetime 'a as defined on the block at 16:47
test.rs:16   fn to_c(&self, r : &'a Ref) -> Container<'a> {
test.rs:17     self.c
test.rs:18   }
error: aborting due to previous error

I have tried many variations and just can't get this thing to compile. I found another post here (How to fix: expected concrete lifetime, but found bound lifetime parameter) but is appears to get around the problem instead of solving it. I can't really see why the problem even originates. The &Ref is being passed along via moves so it should just work right?

Any ideas? Thanks for all the help.

1

1 Answers

14
votes

Let’s compare the two definitions. First, the trait method:

fn to_c<'a>(&self, r: &'a Ref) -> Container<'a>;

And the implementation:

fn to_c(&self, r: &'a Ref) -> Container<'a>;

See the difference? The latter doesn’t have <'a>. <'a> has been specified elsewhere; the fact that it has the same name does not matter: it is a different thing entirely.

Functionally, your trait definition says that the returned container will have a reference inside it to something from r, but nothing from self. It may use self inside the method, but it may not store any references to it in the returned value.

Your method definition, however, is using a 'a that ties the lifetimes of r and the returned Container to self (that is, to the object itself, not the reference—the ρ₂ in &'ρ₁ T<'ρ₂>—it’s a subtle but sometimes significant difference), whereas the trait definition had no such connection.

The two can be made to match by inserting the <'a> in the method definition in the implementation. But bear in mind that that is shadowing the 'a from ContainerB<'a>; it is not the same 'a! We’re better to give it another name; for convenience, I’ll make the change the other way round, changing it on the impl instead of the method (either would do):

impl<'b> ToC for ContainerB<'b> {
    fn to_c<'a>(&self, r: &'a Ref) -> Container<'a> {
        self.c
    }
}

But now of course you have a problem: the return value is of type Container<'b> (because that’s what the field c in a ContainerB<'b> is), but your signature demands Container<'a> (something using a reference from r, not from self).

One way which would fix it is specifying the lifetime of &self as 'a in both the trait definition and the implementation; in the implementation, this would then demand that 'b was greater than or equal to 'a (by virtue of the fact that you have successfully taken a reference with lifetime 'a to an object with lifetime 'b, and the object must outlive the reference) and so due to the subtyping ('a is a subtype of 'b) Container<'b> would be safely coerced to Container<'a>.

These sorts of lifetime matters are difficult to think about when you’re not familiar with them; but in time they become quite natural.