Which borrow is exactly the issue here?

From what I understand, the following code is problematic because root is borrowed both mutably and immutably inside a loop. However, the error message is a bit more cryptic, because it seems to also involve the unrelated variable scene.

    let mut root = Root::new(device, queue, surface, &config, playback);
    let mut scene = Scene { instances: vec![] };
    let mut hud = Hud {};
    loop {
      update(&mut root);
      sync(&root, &mut scene, &mut hud);
      render(&root, &scene, &hud);
    }

Here is the diagnostic.

error[E0502]: cannot borrow `root` as mutable because it is also borrowed as immutable
  --> src\main.rs:62:22
   |
62 |       update::update(&mut root);
   |                      ^^^^^^^^^ mutable borrow occurs here
63 |       sync::sync(&root, &mut scene, &mut hud);
   |                  -----  ---------- immutable borrow later used here
   |                  |
   |                  immutable borrow occurs here

Is the checker really complaining about scene? Why? What's the deal with scene?

Thanks!

2 Likes

without the signature of function sync() and render(), it cannot be determined whether scene is related or not.

2 Likes

Thanks! The issue is probably then that theoretically the scene can reference stuff owned by the root. If we look at the prototypes, we see that I was trying to relate them by lifetime, so in that sense, they are related.

fn update(root: &mut Root)
fn sync<'a>(root: &'a Root<'a>, scene: &mut Scene<'a>, hud: &mut Hud)
fn render(root: &Root, scene: &Scene, hud: &Hud)

Does this explain the error message or should I share more? I tried to make a MRE but was not able to trigger the same error in a easy way. The whole thing is quite long and feel bad posting it.

You're shared-borrowing root forever.

Once you do that, you can't take a &mut to root and can't move it either. scene is mentioned because you said the lifetime has to be the same be there, too.

You probably need to get rid of the nested 'a.

2 Likes

Thanks! You are talking about the 'a in root: &'a, right? Yes, that fixes it, thanks! So, now that the signature of sync is as follows:

fn sync<'a>(root: &Root<'a>, scene: &mut Scene<'a>, hud: &mut Hud)

The idea of the sync function was to mutate the scene using objects exposed on the root object. For instance, the scene may contain a house, and the house uses a 3D model and textures defined on the root. Is this signature still capable of supporting this, or am I way off? In order for the scene to be able to reference things found on the root, I needed the lifetime of the root reference itself to be 'a, I think, so that root was guaranteed to be valid as long as the scene was valid. That's why the extra 'a was there. Now that that's gone, can this function still do its job? Or I need to change the signature once again? Thanks!

The problem you will have is not with this function signature, but your goal. You can't both have scene contain references to the contents of root, and mutate root separately in the update() function. References require the referent to not be potentially modified by anything else as long as they exist.

As a general rule for beginners, you should not build your application’s primary data structures out of references, and, therefore, you should never need to write a struct or enum that needs a lifetime parameter unless it is a strictly temporary data structure (that is, one that doesn’t outlive one iteration of your main loop, more or less). There are cases where references can and should be used in such a way, but they are rare and this is not one of them.

What should you use instead of references? It depends on the situation.

  • Rc or Arc if the referenced value itself does not change, even if its container changes. (For example, assets such as meshes, that are loaded once and not mutated thereafter, often fit this description.)
  • For data that is to be mutated, indexes/keys are often the best option even though they introduce the possibility of a lookup failing.
  • Rc<RefCell<T>> and Arc<Mutex<T>> are the blunt instrument of arbitrary shared mutability. They can be very powerful but easy to misuse and they restrict the borrowing you can do. Use these sparingly and only with a plan for how each one fits into your architecture. Wrap them in newtypes designed for each situation, to restrict the usage so as to prevent deadlock and logic bugs.
3 Likes

Thank you, I had a strong feeling that maybe I should adopt Rc.

The reason why I thought it might not be the best option for me, is that in my data model the lifetimes and ownerships are very defined, at least in theory; for instance, root outlives everyone and owns everything, scene only creates nodes that reference object that are on root. But now I see that maybe it is not as simple as I thought. Thanks!

In case it was part of the confusion: Rust lifetimes (those '_ things) relate to the duration of borrows, and not (directly) to the liveness scope of values. Shared-borrowed values (&value) cannot be moved, overwritten, or have &mut _s taken to them, and exclusively-borrowed values (&mut value) cannot be directly used at all.

"Lifetimes" was an unfortunate terminology choice since it doesn't correspond to how other languages use the term, but here we are. The relation between the two concepts is that being destructed is another use of a value which conflicts with being borrowed (but, crucially, not the only use which conflicts).

1 Like