Vec of trait objects with references to non-static structs


#1

I’m trying to have the Sphere structs hold references to Materials (which they don’t own), but I need to keep track of Spheres via their mesh trait objects, because I have also other types of meshes. It looks like below:

struct Material {}

struct Sphere<'a> {
    mat: &'a Material,
}
impl<'a> Sphere<'a> {}

trait Mesh {}
impl<'a> Mesh for Sphere<'a> {}

struct MeshList {
    list: Vec<Box<dyn Mesh>>,
}

pub fn main() {
    let lamb = Material {};

    let meshes: Vec<Box<dyn Mesh>> = vec![Box::new(Sphere {mat: &lamb})];

    let list = MeshList { list: meshes };
}

Here is a link to the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=281a5a288d666b2027c37b07b9028086

Trying to run this gives me the good old

error[E0597]: `lamb` does not live long enough
      --> src/main.rs:18:65
       |
    18 |     let meshes: Vec<Box<dyn Mesh>> = vec![Box::new(Sphere {mat: &lamb})];
       |                                           ----------------------^^^^^--
       |                                           |                     |
       |                                           |                     borrowed value does not live long enough
       |                                           cast requires that `lamb` is borrowed for `'static`
    ...
    21 | }
       | - `lamb` dropped here while still borrowed

I believe the problem is due to how these trait objects (and the meshes vec) receives a static lifetime by default, and the ‘lamb’ receives a regular lifetime. When the scope ends it would get released right before the 'static stuff does, which Rust doesn’t like.

How can I tell Rust that I want those meshes to live at most as long as lamb? If there is no way of doing that, what is the Rust idiomatic way of doing something like this?

Thanks!


#2

Just add a lifetime parameter to the inner type of the Box:

struct MeshList<'a> {
    list: Vec<Box<dyn Mesh + 'a>>,
}

#3

Thanks, that seems to do it.

I don’t get what is happening though. That says that the lifetime of MeshList is tied to the lifetime of the Boxes, correct? Will that just make the ‘meshes’ object have a 'static lifetime? Why doesn’t that just cause the same problem, as ‘list’ will be collected just the same way as ‘meshes’ will.

Additionally, why is it that if I don’t add the MeshList object into the scene, it compiles normally?


#4

Boxes are not necessarily 'static. They are static by default, i.e. when you write Box<T>, Box<T + 'static> is implied. But when you specify another lifetime, the 'static requirement is lifted.

In this case, the compiler sees that Vec<Box<T + 'a>> is destroyed before the 'a lifetime ends, so there is no error. If you tried to pass the vector to something that requires static lifetime (e.g. to thread::spawn), the compiler would prevent you from doing that.

Can you provide the code for that? There seems to be no scene in your original example.


#5

In this case, the compiler sees that Vec<Box<T + 'a>> is destroyed before the 'a lifetime ends, so there is no error.

I think I understand. So when I declare MeshList with that lifetime like you did, it will actually work the other way around to what I was expecting: It will just “connect” the lifetime of the meshes object to the lifetime of the Boxes, meaning they won’t be static anymore and everything will compile. Is that correct?

Can you provide the code for that? There seems to be no scene in your original example.

Just a figure of speech, I meant that this seems to compile: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5093db4c55926e31acb37c1a22a38a00

Does it compile just because the compiler is optimizing out all these unused variables? Because I expected the boxes in the meshes object to have 'static lifetime (no MeshList object to tie their lifetimes to something in the scope), so I also expected this to fail.


#6

Yes.

Curiously enough, Box<T> in a type annotation doesn’t imply 'static. Your meshes variable is still not static. If you write let meshes: Vec<Box<dyn Mesh + 'static>> = ..., the error will come back.


#7

It looks like dyn Trait (without + 'lifetime) is like dyn Trait + '_ (i.e., the lifetime is ellided). Within a function’s body, it will use lifetime inference to find a matching lifetime (that’s why it worked in your last example), whereas in struct / type definition, it will pick 'static. Hence the difference.