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 Material
s, since there won't be that many of them and though they're relatively simple and Copy
able, 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.
Reference lifetime. As a reference lifetime 'static indicates that the data pointed to by the reference lives for the entire lifetime of the running program. It can still be coerced to a shorter lifetime.
A static item is similar to a constant, except that it represents a precise memory location in the program. A static is never "inlined" at the usage site, and all references to it refer to the same memory location. Static items have the static lifetime, which outlives all other lifetimes in a Rust program.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With