Having a struct where one member refers to another

Let's say I have a struct Mesh that holds a reference to a struct Geometry, making the below possible.

let geometry = Geometry::new();
let mesh1 = Mesh::new(&geometry);
let mesh2 = Mesh::new(&geometry);
let mesh3 = Mesh::new(&geometry);

Then let's say I want to have a struct Player that holds a Geometry and a Mesh. The most natural way (at least for me) would be to have the player own a Mesh and Geometry and encapsulate creating them in the Player struct like so:

pub struct Player<'a> {
	geometry: Geometry,
	mesh: Mesh<'a>
}

impl<'a> Player<'a> {
	pub fn new() -> Self {
		let geometry = Geometry::new();
		let mesh = Mesh::new(&geometry);

		Self {
			geometry,
			mesh
		}
	}
}

This results in a compile time error

cannot return value referencing local variable geometry
returns a value referencing data owned by the current function

Which makes sense to me if I was only returning a Mesh, but I'm returning a Mesh and the Geometry it refers to together. That way, the geometry is moved out of the function scope so it won't be deallocated to invalidate the reference. Either I'm misunderstanding something or Rust isn't smart enough to recognize this should be okay.

This is a scenario that will happen a lot with my project, having a struct that owns both a Geometry and a Mesh. So my question is, what are some suggestions I could implement that would fix this problem?

I can see two options, neither of which I really like.

Option 1 - Have Mesh own a Geometry
This would eliminate the reference but many Meshes would then not be able share a single Geometry. Not ideal if you have a large Geometry you want to be used by many Meshes since you would have to create multiple copies of the same Geometry.

Option 2 - Lift the Geometry to a higher scope
The Player's new function could take in the geometry as a reference but now you'd have to create it outside the Player so you've lost the nice encapsulation of keeping it inside and now more work is required to create a Player struct. You could alleviate this a little bit by changing the Player to the following.

pub struct Resources {
	geometry: Geometry
}

pub struct Player<'a> {
	mesh: Mesh<'a>
}

impl<'a> Player<'a> {
	pub fn create_resources() -> Resources {
		Resources {
			geometry: Geometry::new()
		}
	}

	pub fn new(resources: &'a Resources) -> Self {
		let mesh = Mesh::new(&resources.geometry);

		Self {
			mesh
		}
	}
}

This way the creation of the Geometry is sort of encapsulated in Player. But you still have the undesired pattern of needing to call create_resources() before calling new() and moving ownership of the Geometry to a higher scope.

Is there a better option that allows both to hold:

  • A Mesh holds a reference to a Geometry
  • The Mesh and Geometry are fully encapsulated in the Player

Can I satisfy both or will I need to make a sacrifice?

1 Like

Generally it is not possible in safe Rust to make a self-referential struct, i.e. a struct where one of the fields contains references into a struct itself.

Have a look here When is a linked list not a linked list? where I present the geometry of the Hunt The Wumpus Game in Rust.

It's rude and crude and simple but at least shows one way to make structs refer to others of the same type. Not with references but with array indices. Thus keeping the compiler happy.

I'm not an expert but I understand that are (at least) a couple other problematic situations. For one, a function like this is perfectly valid:

fn drop_geometry_and_use_mesh(player: Player) {
    drop(player.geometry);
    println!("{:?}", player.mesh)
}

though obviously not correct if mesh is borrowing from geometry. But there's nothing that indicates to the compiler that that is the case.

The other is that the address of Player.geometry may change when the player struct is moved. In that case the reference in mesh would become invalid.

If you want to share one Geometry between lots of places the most straightforward way us to use std::sync::Arc. Then the player struct would look something like this

pub struct Player {
    geometry: Arc<Geometry>,
    mesh: Mesh, // Mesh contains an Arc<Geometry> as well
}

A reference is basically just a pointer; it's a variable that stores the address of something else in memory. Consider the following code:

pub fn new() -> Self {
	let geometry = Geometry::new();
	let mesh = Mesh::new(&geometry);

	Self {
		geometry,
		mesh
	}
}

mesh has a reference to the geometry variable which is stack-allocated in new(). That geometry value then gets moved into a Player struct. But the reference inside mesh still points to the old location on the stack.

Now, maybe you could update the reference after creating the Player so that it points to the player.geometry instead. But then what happens when you return that Player from new()? It gets moved (i.e., copied) from the new() stack frame into the caller's stack frame, but the reference inside player.mesh still points to the old location inside a stack frame that no longer exists.

This is why it generally doesn't make sense for one field of a struct to hold a reference to another field: Structs can move around in memory, which invalidates any self-references.

You can only keep a reference to something that doesn't move during the lifetime of the reference.

I see two basic solutions to your problem. You've already hit upon one of them: Have the Geometry instance live outside the Player, and have the Player contain a reference to it.

Another solution would be not to have Mesh store a reference to a Geometry but rather have one passed in to any methods that use it. This would allow the Player to continue to own the Geometry.

A variation on this is to have Mesh hold an Rc<Geometry> (or Arc). There’s a little bit of extra cost when creating and destroying the mesh (to update the ref counts), but it should be equivalent to &Geometry for other operations.

Slightly more advanced (and probably not worth the complexity), you could make Mesh generic over any type that implements Borrow<Geometry>, which lets the downstream code decide between &, Rc, and ownership on a case-by-case basis.

Thanks for all the great replies! I've definitely learn a lot about how addresses are stored. I think for now I'm going to have the Mesh own a Geometry so no reference is needed. Then when I implement instanced rendering (which is really the use case for multiple meshes sharing a single geometry) I'll consider a more intricate system of managing geometries. Perhaps I'll have some kind of InstancedMesh which doesn't hold a reference to a geometry, but an index into a list of geometries stored somewhere outside the player. I thought of another idea where you have a struct that holds a geometry and a list of meshes. Thereby linking the single geometry to multiple meshes not by a reference but by a container object.

I'll have to consider these options but thanks for all the help! I think this is a great example of how Rust forces you to change your implementation so it can keep all the great safety grantees in your application.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.