Where to store common resources (shading material in this case) with references to them?

Hi, Rustaceans! I just started Rust and computer graphics and am implementing a raytracer. The type hierarchy is as below:

// To be moved into a closure used by winit event loop
// Contains the scene and material (or I'm tring to put it in).
struct  World {/*snip*/}
// Contains camera, light sources, triangles, other polygons and spheres.
struct Scene {/*snip*/}
// Contains three vectors and a reference to material (or I'm tring to put it in).
struct Triangle {/*snip*/}
// Contains diffuse coefficient, specular coefficient, phong exponent, etc.
struct Material {/*snip*/}

The size of a triangle object is very small and duplicate material for each triangle is not wise, so I'm tring to make them store a reference to a material struct. This is where my problem arises.
To let Triangle store a reference, it has to have a named lifetime parameter:
struct Triangle<'a> {/*snip*/}
So does Scene, which contains a Vec<Triangle<'a>>:
struct Scene<'a> {/*snip*/}
And so does World. But the problem is, where to store the actual Material object? I tried to load triangles and material from a file in World::new():

impl<'a> World<'a> {
    fn new() -> Self {
        //using Box so as to keep the actual material in place when moving
        //this variable into the World instance, as one would use malloc in C.
        let material: Material = Box::new(read_material(&file));
        let mut triangels: Vec<Triangle<'a>> = read_triangles(&file);
        link_material_and_triangles(&mut triangles, &material);
        //create camera etc.
        Self { material, triangles, /*snip*/}

This won't work: the compiler complains that material is moved in Self { material, triangles, /*snip*/} while referenced by triangles.
Also, the winit eventloop requires that the closure it accepts should be of lifetime 'static:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
   --> src/main.rs:59:19
59  |             world.draw(pixels.get_frame());
    |                   ^^^^

How do I fix this? Or where should common resources as material in this case go in Rust?

There may be ways to get it to work using a reference but it is going to be quite annoying with lifetime handling and 'static bounds.

The simplest solution is just to wrap the Material in an Rc (or Arc if you are doing multi-threading). This provides what you're already trying to do with Box. Unlike Box which is designed for cases where there is only one owner, Rc and Arc are designed for situation like this were you have many references to the same data.

1 Like

Rust's references are temporary scope-bound loans. They are not the same thing that other languages use for storing objects "by reference".

If you try to use Rust's scope-bound temporary loans in structs, your code will explode with <'a> annotations everywhere, and usually won't even compile, because Rust forbids objects from borrowing from themselves (AKA self-referential structs).

Most of the time you will need to use Rc (single-thread only) or Arc (universal) to access objects by reference from multiple places. Alternatively, don't store references, but require the references to be passed as arguments to every function using them, and/or refer to objects indirectly, e.g. by their numeric index in another array.


Oh! Thanks! After like two hours' struggle I finally made it work, but I have to wrap every single one triangle's member. It's not a problem in this case because I'm rendering less than a thousand triangles now, but I'm worried that the unnecessary counting can cause performance issues when rendering more of them.

Is there a more elegant way to do that? And how would a game engine or software renderer in Rust do that? (I tried to read bevy's source code but I don't think I can understand it before I finish the Rustonomicon)

"your code will explode with <'a> annotations everywhere" --yeah it did when I was trying to do that...

I've solved it with Rc, but I'm still seeking a better approch in terms of performance. Could you explain what you meant by "refer to objects indirectly"? Like storing them in a Vec and refering to them by indexing?

This would be something like instead of storing and Rc in each triangle, you'd store a usize. The value of the usize would be the index of the corresponding Material in the Vec<Material> that is stored in World. That way you avoid the reference counting that may indeed be a performance issue, and will also avoid creating many small allocations and you'll just have 1 large one (the Vec). The downside is that whatever function uses your triangle will also need a reference to the World so that it can get the Material it needs.

1 Like