Multiple mutable borrows but why?

Hello there,

In the example bellow, the borrow checker is angry at me for attempting to make multiple mutable borrows, however I'm failing to see where I'm actually borrowing here, other than the initial one.

struct Foo {
    value: Option<String>,
}

impl Foo {
    fn next(&mut self) -> Option<&str> {
        // ...
        self.value.as_deref()
    }
}

fn main() {
    let mut foo = Foo {
        value: None,
    };
    
    let mut bar: Vec<Option<&str>> = Vec::new();
    
    bar.push(foo.next());
    bar.push(foo.next());
}

The error:

error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> src/main.rs:39:14
   |
38 |     bar.push(foo.next());
   |              ---------- first mutable borrow occurs here
39 |     bar.push(foo.next());
   |         ---- ^^^^^^^^^^ second mutable borrow occurs here
   |         |
   |         first borrow later used by call

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

Also the example bellow works just fine, but if I try to push any of those variables I get the same error. I guess I lack some understanding of what actually happens when you pass something to a function ? I don't know what I'm missing.

let a = foo.next();
let b = foo.next()

The compiler is perfectly right to be angry at you.

The signature of your next function is

fn next(&mut self) -> Option<&str>

which is sugar for

fn next<'a>(&'a mut self) -> Option<&'a str>

That means the function signature says the &str in the output is potentially borrowed from self. And indeed, it is.

So after the first time you push foo.next() into bar, bar contains an element with a reference into foo's content. After that, you try to call foo.next() again, which takes a mutable reference (&mut self). You have both a mutable reference and an immutable reference to the same object, so you're violating the borrowing rules.

4 Likes

PS: Here is a concrete case where you could cause UB if you could compile code like this:

struct Foo {
    value: Option<String>,
}

impl Foo {
    fn next(&mut self) -> Option<&str> {
        self.value = Some(String::from("foo"));
        self.value.as_deref()
    }
}

fn main() {
    let mut foo = Foo { value: None };
    let mut bar: Vec<Option<&str>> = Vec::new();
    // Changes foo's inner value to Some(String::from("foo"))
    // and pushes a reference to the inner "foo" into bar.
    bar.push(foo.next());
    // Drops the old "foo" and replaces it with a new "foo".
    // The old reference is now dangling, oops!
    bar.push(foo.next());
}
2 Likes

Thank you ! That made it much clearer.

I wonder if the error I got isn't a bit misleading though, as this seems to be a case of having an immutable reference and a mutable one simultaneously rather than having two mutable ones ? If that's the case I suspect the compiler should have thrown another error here.

Also worth reading: https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#9-downgrading-mut-refs-to-shared-refs-is-safe (the link points to the relevant section for this thread [note that the title quotes a misconception], but the whole thing is worth reading ^^)

3 Likes

For the purpose of these errors, it’s the original (mutable) borrows that matters and that is (logically) kept alive by the returned &str that has the same lifetime. In your code example, even immutable access would lead to a conflict:

fn main() {
    let mut foo = Foo {
        value: None,
    };
    
    let mut bar: Vec<Option<&str>> = Vec::new();
    
    bar.push(foo.next());
    &foo;
    &bar;
}
error[E0502]: cannot borrow `foo` as immutable because it is also borrowed as mutable
  --> src/main.rs:20:5
   |
19 |     bar.push(foo.next());
   |              ---------- mutable borrow occurs here
20 |     &foo;
   |     ^^^^ immutable borrow occurs here
21 |     &bar;
   |     ---- mutable borrow later used here

This rule/restriction is not really necessary for avoiding UB in the concrete case at hand, but in the general case, even an immutable reference can maintain mutable access, and the borrow checker rules are applied consistently, without looking into a function, or caring too much about the specifics of a type.[1] A typical example is Cell::from_mut(t: &mut T) -> &Cell<T>.


  1. The motivation for this is to allow changes for implementation details (e.g. the code in a function, or the private fields of a type) without breaking the API of your methods. ↩︎

3 Likes

No; it doesn't matter that the return type is immutable. All that matters is that it has the same lifetime as the &mut self borrow. The compiler uses lifetimes to reason about the extent of borrows. Thus, keeping around an immutable &'a str will keep the mutable &'a mut self active as well.

(Why it works like that is, in short: because a lot of unsafe code already relies on this property for safety.)

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.