Mutable and immutable borrow on same line?

Hi,

I'm new to Rust. I'm trying to understand what I'm doing wrong in the code below. The error I get is not clear to me. Specifically, it seems to indicate that the line parent.count_children() is an immutable borrow and also a mutable borrow. I've gone through the rust docs and tried to relate what I'm doing to the borrowing and lifetime sections of the docs, but something is not clicking. For example, I'm naming the lifetime parameters after what I think is the lifetime they should be describing (ex. 'child for the Child struct), but I'm not sure that is right. I've read through the help for E0502 but that still doesn't seem to explain why the code is wrong.

The code is basically trying to have a parent object that owns some assets, and shares those asset with child objects. The parent can add children and report how many children it has. Since the parent maintains the lifecycle of the child objects, the children should not live beyond the lifecycle of the parent and therefor should not be at risk of referencing assets that have gone out of scope when the parent goes away.

I've tried a simpler example where the parent struct has the same interface (new, add_child, count_children) but internally just has a list of i32 for children. That compiles and runs just fine. So there is something about having references to other structs that is causing the problem.

Why does the following code fail to compile? What does the compiler message referring to when the ^ are underneath the whole line? Where is the gap in my understanding of borrowing?

use std::fmt::{Display, Formatter};

#[derive(Debug)]
struct Assets {
    assets: Vec<i128>,
}

struct Child<'child> {
    assets: &'child Assets,
}

impl<'child> Child<'child> {
    fn new(assets: &'child Assets) -> Self {
        Child { assets }
    }
}

impl Display for Child<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.assets)
    }
}

struct Parent<'child> {
    children: Vec<Child<'child>>,
    assets: Assets,
}

impl<'child> Parent<'child> {
    fn new(assets: Assets) -> Self {
        Parent {
            children: vec![],
            assets,
        }
    }

    fn add_child(&'child mut self) {
        self.children.push(Child::new(&self.assets));
    }

    fn count_children(&self) -> usize {
        self.children.len()
    }
}

fn main() {
    let mut parent = Parent::new(Assets { assets: vec![] });
    parent.add_child();
    parent.count_children();
}


(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `parent` as immutable because it is also borrowed as mutable
  --> src/lib.rs:49:5
   |
48 |     parent.add_child();
   |     ------ mutable borrow occurs here
49 |     parent.count_children();
   |     ^^^^^^
   |     |
   |     immutable borrow occurs here
   |     mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` (lib) due to previous error

Thanks!!

For example, I'm naming the lifetime parameters after what I think is the lifetime they should be describing (ex. 'child for the Child struct), but I'm not sure that is right.

This is a good principle. But note that when thinking about lifetimes, you need to care about which borrows the lifetime describes, not which values. So, your 'child lifetime might be better named 'asset as it is the lifetime of the borrows of the Assetses.

Since the parent maintains the lifecycle of the child objects, the children should not live beyond the lifecycle of the parent and therefor should not be at risk of referencing assets that have gone out of scope when the parent goes away.

That's what happens by default because the parent owns the child. The &'child Assets reference being involved here doesn't affect that.


Now, as to understanding the actual problem here: The 'child lifetime is really just a part of the type of the children. It mustn't be used in any other, unrelated borrows, but you've done that here:

    fn add_child(&'child mut self) {

This is saying that, in order to call this method, self is borrowed until the lifetime 'child ends. But, self is of type Parent<'child>, so you've constructed the reference type &'child mut Parent<'child> — meaning that the 'child lifetime must both outlive and be outlived by the Parent. So, the Parent becomes borrowed for the rest of its existence, which creates the weird errors you see.

You must not conflate the lifetime of a mutable borrow (&'this_lifetime mut Foo<...>) with a lifetime used in the type of the borrowed value (&mut Foo<'this_lifetime>).


Now, you probably did that for a reason, and the reason is that you're trying to borrow self.assets in each Child. You cannot do that. This is what we call a “self-referential struct” — it holds a value that borrows itself. You cannot write such a struct in plain Rust code. It's not how borrows are meant to
be used — borrows instructs need to borrow other already-existing things that outlive the entire struct, and those already-existing things need to “hold still” while they're borrowed — not be be moved, not be mutated and not even have a mutable reference taken to them — but add_child is taking a mutable reference to the Parent, which includes its assets field even though that field is not specifically being mutated.

There are a few solutions here, but which one is appropriate depends on what you actually want to do with Assets. One is to share it with the Arc or Rc pointer types — you would use them where you are currently using &. Another is to not store the Assets at all in the Children, and merely pass them in to the functions that need them. Tell us more about your goals, and we can try to suggest a suitable solution.

3 Likes

this is an anti-pattern, you end up creating a self-borrowed data. remove the explicit lifetime on the self.

Thanks for the reply. I think I'm still trying to wrap my head around it.

The example is a bit contrived but basically I want the parent to be responsible for managing the children and routing calls to those children. The children will have operations to track some state (within a specific child). The validation that those operations are legal is done using the assets. The list of assets are know at the time the parent is created, and don't change during the parent's lifetime. The children are responsible for validating and carrying out the operations.

I could pass the asset to each call of the child but that seems unnecessary to have an extra parameter for each child method if the assets are static for the life of the child. It seems cleaner to construct the child with the assets it needs and keep each method signature focused on the intent of that method. This makes testing the child a bit cleaner as well. And since the parent is responsible for the creation of the child object, it also needs to know the assets.

I could pass a copy of the assets to each child but that seems wasteful. I don't want the parent to do the validation for the child because that would couple the parent to the logic of the child. Perhaps Rc pointers are the way to go?

Thanks

Passing a reference to the assets to the child is definitely the right thing to do in Rust. I understand the tendency to store references in each struct to the things that struct's methods need, since in other languages this is commonly done. But in Rust, since it is based on the idea of single ownership, you need to think a little differently, and it is an adjustment.

In your particular case this causes a self-reference struct, as @kpreid said, and this isn't possible in Rust. More generally, holding a reference will prevent the owned object from being changed or destroyed; that might be Ok in this particular case (if it were not for the self-referencing issue), but in general it is not. So it is a good idea to get in the habit of not storing references in structs, except in certain special cases. For example, a reference to a constant is always Ok since the lifetime of the reference is 'static.

Passing a reference parameter is not at all wasteful. The cost of this is close to zero.

Rc should be a great solution. It sounds like your Assets are immutable, so it will work very well with Rc. It’s as easy to use as copying the assets for each child, but copying is super cheap. The overhead of Rc compared to references is very minimal, and probably completely irrelevant compared to what else creation and usage of each Child entails. If the need ever comes up and children need to be handled in different threads concurrently, refactoring to switch to Arc should be simple, but it’s good to start out with just Rc, since Rust will reliably give you compilation errors for misuse of non thread-safe values like an Rc, anyways, if that ever comes up in the future.


Another thing you could do if you want, instead of using Rc<Assets> is to re-write Assets itself to be cheap to copy. You just put the Rc inside and do

struct Assets {
    assets: Rc<Vec<i128>>,
}

On that note, you could even further reduce the number of indirections here by using a Rc<[i128]>.

struct Assets {
    assets: Rc<[i128]>,
}

Values of type Rc<[i128]> are just as useful as Vec<i128> for immutable purposes, as the relevant API comes from the slice type [i128] in either case, anyways. It can be created by converting with .into(), utilizing this conversion implementation. The tradeoff would be that, while removing indirection is beneficial for performance, upon creating a Rc<[i128]> from Vec<i128>, any spare capacity must be removed, so if there was spare capacity, the conversion involves another copy of the data.

The same applies to Arc as well, of course.

The relevant concept is also presented well in this video:

2 Likes

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.