Error trying to use vectors to do processing and then passing it back as part of struct

Hello everyone, so while writing a toy ray tracer, I have run into yet another problem. The snippet below gives an error, saying input_meshes is moved out?, which is then passed as the member of a SceneConfig struct.

My best guess as to why this happening is because I am using a "temporary" object inside a for loop to assign a reference to a struct that obviously outlives the temporary object. What I want to accomplish is that the meshes array will be owned by the SceneConfig struct in question, and I want to somehow assign a reference/pointer to some element inside the meshes vector into a Triangle object. So while Rust is right in the sense that I am trying to assign a reference to a temporary object, I am not sure how I should handle this usecase in an idiomatic way. Any suggestions and help would be appreciated!

src/lib.rs:

pub struct SceneConfig<'a> {
    pub geometries: Vec<Arc<Hitable + 'a>>,
    pub meshes: Vec<TriangleMesh>,
}
...
impl<'b> SceneConfig<'b> {
    pub fn parse_args_and_construct_scene(args: &[String]) -> Result<SceneConfig, Box<dyn Error>> {
	
	//Geometry
    let mut geometries: Vec<Arc<Hitable>> = vec![];
    let mut meshes: Vec<TriangleMesh> = vec![];
	for i in 0..10 {
	let type_of_geometry = "mesh";
	            match type_of_geometry {
                    "mesh" => {
                        let mut input_meshes : Vec<TriangleMesh> = TriangleMesh::new(mesh_absolute_path);
                        meshes.append(&mut input_meshes);
                        for input_mesh in input_meshes.into_iter() {
                            let triangles: Vec<Triangle> = input_mesh.get_triangles_from_mesh();
                            for triangle in triangles {
                                geometries.push(Arc::new(triangle));
                            }
                        }

                    }
                    _ => {
                    }
                }
    }
	Ok( geometries: geometries,
            meshes: meshes,)
}

src/geometry/triangle.rs:


#[derive(Clone)]
pub struct TriangleMesh {
    //Same as tobj::Mesh
    pub positions: Vec<f32>,
    pub normals: Vec<f32>,
    pub texcoords: Vec<f32>,
    pub indices: Vec<u32>,
    pub material_id: Option<usize>,
}

pub struct Triangle<'a> {
    indices: Vec<u128>,
    mesh: &'a TriangleMesh,
}

EDIT: Here's the compiler error:

error[E0515]: cannot return value referencing local variable `input_mesh`
   --> src\lib.rs:164:9
    |
137 |                               let triangles: Vec<Triangle> = input_mesh.get_triangles_from_mesh();
    |                                                              ---------- `input_mesh` is borrowed here
...
164 | /         Ok(SceneConfig {
165 | |             scene_file_name: scene_filename,
166 | |             out_file: out_file,
167 | |             film: film,
...   |
170 | |             meshes: meshes,
171 | |         })
    | |__________^ returns a value referencing data owned by the current function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.

The fundamental issue is that SceneConfig is self-referential. It is a limitation of rust's borrow checker that you cannot have one field with pointers that point into another field.

Option 1: Separate the api into stages

First construct the full Vec<TriangleMesh>. Then construct the full Vec<Triangle<'_>> which borrows from it. The user will have to call two separate functions.

fn construct_meshes(...) -> Vec<TriangleMesh> { ... }
fn construct_triangles(meshes: &[TriangleMesh]) -> Vec<Triangle<'_>> { ... }

// or alternatively, change the `meshes` member of SceneConfig
// to `&'a [TriangleMesh]` and do this:
fn construct_meshes(...) -> Vec<TriangleMesh> { ... }
fn construct_scene(meshes: &[TriangleMesh]) -> SceneConfig<'_> { ... }

Option 2: Remove the reference

Remove the reference from Triangle. Unfortunately, now a triangle can only be interpreted in terms of a TriangleMesh. You will probably need to add an associated type to Hitable so you can supply this metadata:

pub trait Hitable {
    type Meta;

    fn foo(&self, meta: &Self::Meta);
}

impl Hitable for Triangle {
    type Meta = TriangleMesh;

    fn foo(&self, mesh: &Self::Meta) { ... }
}

There will probably be other kinks to work out.

Option 3: Use a continuation rather than returning

This is a neat little dinner party trick; you can simulate returning a borrow to a temporary by making the user pass in a callback. The references will only be alive for the duration of the callback.

pub struct SceneConfig<'a> {
    pub geometries: &'a [Arc<Hitable + 'a>],
    pub meshes: &'a [TriangleMesh],
}

pub fn with_scene_from_args<B, E: Error>(
    args: &[String],
    cont: FnOnce(SceneConfig<'_>) -> Result<B, E>,
) -> Result<B, Box<dyn Error>> {
    // construct all of the TriangleMeshes,
    // then construct all of the Triangles,
    // then call cont() with borrowed forms of the vectors
}

This option can be limiting for consumers. It's great for private code, but I would try to avoid it in public APIs unless absolutely necessary.

Option 4: Use a crate that provides self-references

The crate rental provides a safe interface for defining certain forms of self-referential structures. I've never used it myself, but the option is there.

2 Likes

Thank you so much for the detailed answer!

In the end, I decided to go with something similar to Option 2. There be no TriangleMesh anymore, but the data of all the stuff in TriangleMesh are stored in a vector of Triangles. This bypasses the self-referential struct issue altogether.

While this will take some more memory, someday I'd like to come back and do a proper implementation, once I have gained a few more levels. :wink: Thanks again~

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.