3

I'm trying to understand why I receive an error when attempting to compile the following code

trait Foo<'a> {
    fn foo(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl<'a> Foo<'a> for SomeType {
    fn foo(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo<'a>));
struct Container<'a> {
    pub objs: Vec<Box<dyn Foo<'a>>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o = &c.objs[0].as_ref();
    let x = o.foo();
}

I receive the error

error[E0597]: `c.objs` does not live long enough
  --> src/main.rs:21:14
   |
21 |     let o = &c.objs[0].as_ref();
   |              ^^^^^^ borrowed value does not live long enough
22 |     let x = o.foo();
23 | }
   | -
   | |
   | `c.objs` dropped here while still borrowed
   | borrow might be used here, when `c` is dropped and runs the destructor for type `Container<'_>`

I'm confused as to why c.objs is still borrowed at the end of main. It's my understanding that x will be dropped first, followed by o, which means no references to c should exist at that point, allowing c to finally be dropped without issue.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
captaine
  • 39
  • 4
  • While I don't fully understand the error message, it is related to using the same lifetime `'a` for everything. Removing it from everywhere except `RefHolder<'a>` will make it compile, is this whay you want? – rodrigo Mar 27 '20 at 11:51
  • This code feels very weird to me (struct with a ref to something that can create more refs of itself all with the same lifetime parameters), but I'm beginning to think it might actually be revealing a compiler bug. In particular, if you introduce explicit nesting scopes after each line in `main`, you still get the error when nothing except `c` exists in the top scope of `main`. – JMAA Mar 27 '20 at 14:42
  • https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1c6000be23a260760d89db8e1a65f597 – JMAA Mar 27 '20 at 14:43
  • Here's a more complete example showing what I'm looking to do. In essence, I want to store boxed trait objects, and have a method on said trait that will maybe insert a reference to said trait object into a provided list. Though the code is a bit different, I get the same error "borrowed value does not live long enough" https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d270280cdee26902a207ecee80111997 – captaine Mar 27 '20 at 15:02
  • Note that I've "fixed" the issue by having FooRef hold it's index in the Container's Vec, instead of a reference, but I'd like to know why it doesn't work with references, as that would be preferable (also to further my understanding of Rust's semantics) – captaine Mar 27 '20 at 15:10
  • @captaine, in that last example, try removing all lifetime specifiers then adding them in as the compiler requires them. First, `FooRef` needs one ref it contains, then `FooRefSet` needs one, etc. Eventually you get to `DoesFoo` needing one, at which point `FooRef` needs a second one. There's no good a priori reason I can see that the two lifetimes in `&'a dyn DoesFoo<'b>` should be the same, but unless you set them to the same you have an infinite loop of having to add new lifetimes. I think hidden somewhere there is the crux of the issue: you have an infinite lifetime regression. – JMAA Mar 27 '20 at 17:29
  • 1
    @captaine I think it's a very complicated version of https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct – JMAA Mar 27 '20 at 17:33

2 Answers2

2

When the compiler says that the borrowed value does not live long enough what it actually means is that the lifetime of this value is used beyond its range. The compiler phrases it that way because that is the most usual reason of lifetime violation, but your code happens to be more complicated because of how the lifetime are deduced.

The key line in your code is:

let x = o.foo();

With it, the code compiles fine. Actually it is equivalent to this one

let o : &dyn Foo = c.objs[0].as_ref();
Foo::foo(o);

(the extra & is not needed, but that is not important).

Now the what is the life time of that o reference? Well, since it is initialized from a Box::as_ref() and that is defined as (lifetime unelided):

fn as_ref<'s>(&'s self) -> &'s T

it is the same life time as that of the Box itself, that it is taken from the vector, using the Index trait... so it will eventually be the lifetime of c.objs.

Now, because of the way your trait is defined:

fn foo(&'a self) -> RefHolder<'a>

the returned trait has that very same lifetime. And since every generic in your code uses the same lifetime, so is the lifetime of Container<'a>.

That is, the concrete lifetime of c: Container<'?> is the lifetime of one of its members. That is analogous to a self-referential struct and it is not allowed.

Your code can be made to compile easily simply by removing all lifetimes generics except those that are actually required:

trait Foo {
    fn foo<'a>(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl Foo for SomeType {
    fn foo<'a>(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));
struct Container {
    pub objs: Vec<Box<dyn Foo>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o : &dyn Foo = c.objs[0].as_ref();
    let x = o.foo();
}
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Thank you very much for the explanation. Between your answer and @JMAA's answer I was able to fix it. – captaine Mar 27 '20 at 18:59
1

This isn't a full answer, because I don't fully understand the source of the error, but essentially I now believe the compiler was cryptically telling you it didn't have enough information to resolve lifetimes.

I find that it's often helpful to remove all lifetimes and add them back in piecemeal only as needed to better understand what's going on, so let's do just that.

Start:

trait Foo {
    fn foo(&self) -> RefHolder;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder {
        RefHolder(self)
    }
}

struct RefHolder(&(dyn Foo));

// ...etc

Clearly, RefHolder needs a lifetime parameter due to containing a borrow, so adding those in:

trait Foo {
    fn foo(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Now foo() needs one. Note, not Foo, just the function.

trait Foo {
    fn foo<'a>(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a>(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Now the compiler will tell us much more directly that it can't figure out the lifetime of the return value of foo in the impl. It will say it needs to live as long as 'a (because of the signature) and also as long as the anonymous lifetime of &self (because it's returning self). But it has been told nothing about how those lifetimes relate to each other. So we tell it:

trait Foo {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Now everything's happy. What we've done is said 's must be at least as long as 'a (otherwise self could be destroyed before the RefHolder referring to it). The borrow checker check's that's the case when we call it, and all is well with the world.

Before, you started adding lifetimes to the trait Foo, which is a losing battle because you'll end up with an infinite regress as RefHolder suddenly needs to be told how long its dyn Foo will want to be borrowed for, which isn't something you know ahead of time.

Still not exactly sure why this adds up to the exact error you first saw, but I'm happy to have at least partially disentangled this.

JMAA
  • 1,730
  • 13
  • 24
  • Thank you so much for the detailed explanation. It seemed the missing piece was removing the lifetime from the Trait, and only having it on the `foo`, as well as dropping the lifetime annotation from `Container`. Though I'm confused what the difference is between the two (lifetime on trait vs lifetime on function in trait). Updated example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dd65097d35caab8c873eaf9014b08511 – captaine Mar 27 '20 at 18:57