6

I'm working on a toy ray tracer project in Rust and am hung up on a lifetime-related error. I've stripped down my code to the following self-contained failing case:

struct Material {}

pub struct Sphere<'a> {
    material: &'a Material,
}

pub trait AnySceneObject {}

impl<'a> AnySceneObject for Sphere<'a> {}

pub struct Scene {
    objects: Vec<Box<AnySceneObject>>,
}

fn main() {
    let material = Material {};
    let boxed_sphere: Box<AnySceneObject> = Box::new(Sphere { material: &material });
    Scene { objects: vec![boxed_sphere] };
}

which complains

error[E0597]: `material` does not live long enough
  --> main.rs:17:74
   |
17 |     let boxed_sphere: Box<AnySceneObject> = Box::new(Sphere { material: &material });
   |                                                                          ^^^^^^^^ does not live long enough
18 |     Scene { objects: vec![boxed_sphere] };
19 | }
   | - borrowed value only lives until here
   |
  = note: borrowed value must be valid for the static lifetime...

error: aborting due to previous error(s)

I want to use traits to define objects in the scene, but I want the Scene object to own them. My current understanding is that this means I need Box or something equivalent because trait objects are of unknown size.

I also want objects to share references to Materials, since there won't be that many of them and though they're relatively simple and Copyable, I don't want literally tens or hundreds of thousands of identical copies of the same thing (hence using &'a Material).

I'm confused why it's problematic to pass &material here though: since values are dropped latest-first, wouldn't Scene be dropped first, allowing boxed_sphere to be dropped (since it now owns a Vec that owns that Box), which it then is, allowing material to be dropped, no problem? It seems like it should live at least as long as the other two values in the function, since I'm holding onto the value with the name material for the scope of the whole function.

Also somewhat confusingly, commenting out the instantiation of Scene fixes the issue for reasons I don't understand.

skelley
  • 475
  • 5
  • 15

1 Answers1

8

First of all, if you have hundreds of thousands of scene objects, putting them in a box (which is basically a heap object) is definitely not a good idea.

The error is called because the Box's content must not have any reference that might expire. You can move a Box around and it may never be deleted until the end of the process, so any references it holds must have 'static lifetime.

You can fix it by using Box<T + 'a> to indicate that it will have a limited lifetime:

pub struct Scene<'a> {
    objects: Vec<Box<AnySceneObject + 'a>>,
}

You can also use Vec<&Trait> to store a collection of references to different objects implementing a trait. The following code compiles:

pub struct Scene<'a> {
    objects: Vec<&'a AnySceneObject>,
}

fn main() {
    let material = Material {};
    let sphere = Sphere { material: &material };
    Scene {
        objects: vec![&sphere] 
    };
}

If you know all possible implementations of your trait, you can replace it with a enum. This would make the code more performant, as you would have a vector owning enums instead of references.

Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
  • Questions! (1) Aren't these objects going to be on the heap anyway? Does `Box` add a second level of indirection, in that case? (2) In your second code snippet, it works because `main` owns a single `sphere` handle, but if I dynamically generate a `Vec<&AnySceneObject>` (say, from a file), something needs to own the objects (right?), hence my use of `Box` (I realize this was elided in my simplified example). (3) Where does inferred `'static` come from? The fact that this is in `main`? (4) Enums sound better for this. Where can I read up on how they compare to references in this context? – skelley Oct 27 '17 at 19:16