Help with passing around reference to owned value between structs


#1

I have to admit that I generally avoid messing about with lifetimes if I can help it. Which is probably why it is an area of weakness for me… So now i am trying to convert the following usecase to use references…

I have a manager which looks something like this:

type Mgr = HashMap<String, Box<Material>>; 

Material is a trait…

I have a struct which stores a reference to the boxed material.
pub struct Sphere<'a> {

pub material: &'a Box<Material + 'a>
}
Sphere implements a Shape trait which has a method, hit, that takes a mutable reference to a HitRecord.
fn hit(&self, &mut HitRecord);

The HitRecord also stores a reference to an Option wrapped reference to a Boxed Material
pub struct HitRecord<'b> {

material: Option<&'b Box<Material + 'b>
}

The material gets created first, and stored in a Mgr instance.
The sphere gets created next, with a reference to a material in the Mgr.

in an inner scope, I create a mutable HitRecord. I then call sphere.hit(&mut HitRecord).
the hit method attempts to set the HitRecord’s material field to the sphere’s material record
hit_record.material = Some(self.material)
And this is where things go tragically wrong. I really don’t quite know how to express the lifetimes. I a have two objects that hold on to a reference to a third object which outlives them. I get something like:

hit_record.material = Some(self.material);
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the lifetime 'a as defined on the impl at 17:1…
–> src/sphere.rs:17:1
|
17 | impl<'a> Shape for Sphere<'a> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: …so that the type std::boxed::Box<traits::Material + 'a> is not borrowed for too long
–> src/sphere.rs:23:36
|
23 | hit_record.material = Some(self.material);
| ^^^^^^^^^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #3 defined on the method body at 22:5…
–> src/sphere.rs:22:5
|
22 | / fn hit(&self, hit_record: &mut HitRecord) {
23 | | hit_record.material = Some(self.material);
24 | | }
| |_____^
= note: …so that the expression is assignable:
expected std::option::Option<&std::boxed::Boxtraits::Material>
found std::option::Option<&std::boxed::Boxtraits::Material>

here is a github repo if you are curious:
renderer-scratch repo


#2

The hit method should probably be:

fn hit(&self, hit_record: &mut HitRecord<'a>) {
        hit_record.material = Some(self.material);
}

Note the 'a in HitRecord<'a>.

If that doesn’t work, maybe you can put together a reduced playground example.

Edit: I took a look at your repo a bit closer, and the above is correct but you’ll need to modify the Shape trait as well to give it a lifetime and use that lifetime parameter in hit()'s signature.:

pub trait Shape<'a>: Sync + Send {
    fn render(&self) -> String;
    fn hit(&self, hit_record: &mut HitRecord<'a>);
}

impl<'a> Shape<'a> for Sphere<'a> {
    ...
    fn hit(&self, hit_record: &mut HitRecord<'a>) {
        hit_record.material = Some(self.material);
    }

}

#3

Thanks. That worked!