rtviii
rtviii

Reputation: 897

Can I borrow immutably again after a mutable borrow goes out of scope?

inspect is a closure that takes an immutable reference to ball.

edit is a closure that takes a mutable reference to ball.


I know that the borrow checker doesn't let you borrow at all while a mutable borrow is around. My expectation however is that once edit, which is the only "source" of mutable borrows goes out of scope, I should be able to call inspect again. Not so, apparently? Or am I getting something wrong about the closures' lifetimes?

Given that

#[derive(Default, Debug)]
pub struct Ball {
    pub color: String,
    pub size: String,
}

pub fn moves() {
    let mut ball = Ball {
        color: "blue".to_string(),
        size: "small".to_string(),
    };

    let inspect = || {
        println!("Unremarkable. {} ball. ", (&ball).color); // &ball
    };
    inspect(); // OK.

    {
        let mut edit = || {
            ball.color = "red".to_string(); // Not OK if I call inspect later.
        };
        edit()
    }; // <---- I expect edit to go out of scope here
    inspect(); // If I comment this out edit doesn't complain.
}

The error I'm getting is

error[E0502]: cannot borrow `ball` as mutable because it is also borrowed as immutable
  --> src/moves.rs:40:25
   |
34 |     let inspect = || {
   |                   -- immutable borrow occurs here
35 |         println!("Unremarkable. {} ball. ", ( &ball ).color);
   |                                                ---- first borrow occurs due to use of `ball` in closure
...
40 |         let mut  edit = || {
   |                         ^^ mutable borrow occurs here
41 |             ( &mut ball ).color = "red".to_string()
   |                    ---- second borrow occurs due to use of `ball` in closure
...
45 |     inspect();
   |     ------- immutable borrow later used here

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

What am I missing?

Upvotes: 1

Views: 125

Answers (3)

Chayim Friedman
Chayim Friedman

Reputation: 70860

A closure does not borrow when called. It holds a borrow.

It will become clear if we'll desugar the closures manually:

struct Inspect<'a> {
    ball_color: &'a String,
}
impl Fn<()> for Inspect<'_> {
    extern "rust-call" fn call(&self, (): ()) {
        println!("Unremarkable. {} ball. ", *self.ball_color);
    }
}
impl FnMut<()> for Inspect<'_> {
    extern "rust-call" fn call_mut(&mut self, (): ()) { self.call(()) }
}
impl FnOnce<()> for Inspect<'_> {
    type Output = ();
    extern "rust-call" fn call_once(self, (): ()) { self.call(()) }
}

struct Edit<'a> {
    ball_color: &'a mut String,
}
impl FnMut<()> for Edit<'_> {
    extern "rust-call" fn call_mut(&mut self, (): ()) {
        *self.ball_color = "red".to_string();
    }
}
impl FnOnce<()> for Edit<'_> {
    type Output = ();
    extern "rust-call" fn call_once(mut self, (): ()) { self.call_mut(()) }
}


let mut ball = Ball {
    color: "blue".to_string(),
    size: "small".to_string(),
};

let inspect = Inspect { ball_color: &ball.color };
inspect();

{
    let mut edit = Edit { ball_color: &mut ball.color };
    edit();
};

inspect();

The moment we borrowed ball.color mutably for edit, we invalidated the shared borrow of inspect. Any attempt to use it, and that includes calling inspect(), will trigger an error.

Upvotes: 1

Nate Eldredge
Nate Eldredge

Reputation: 57922

As Rubens says, the issue is not what you think it is.

// line A
let inspect = || { 
       println!("Unremarkable. {} ball. ", (&ball).color);   // &ball
};  
inspect();                                                   // OK.                                    
// line B
{ 
    // line C
    let mut edit = || {                                      
        ball.color = "red".to_string();             // Not OK if i call inspect later.
    };
    edit() 
};
// line D
// inspect();                                          // If i comment this out edit doesn't complain.

Since the closure inspect borrows ball immutably, you cannot borrow ball mutably, as edit does, during the lifetime of inspect.

Now based on that, you would think the code would still be invalid even without the second call to inspect at line D, since after all, the lifetime of inspect appears to last until the very end of the function, and to overlap that of edit. However, Rust has a feature called non-lexical lifetimes. The compiler can see that inspect is never used again after line B, and so, as a convenience to the programmer, it ends its lifetime early. Therefore you are allowed to mutably borrow ball at line C, because the immutable borrow from inspect has gone away by then.

If you call inspect again at line D, its lifetime can't end early, and so then the conflict with edit at line C reappears and you have an error.

Upvotes: 2

Rubens Brand&#227;o
Rubens Brand&#227;o

Reputation: 411

I know that the borrow checker doesn't let you borrow at all while a mutable borrow is around.

Yes, but it also don't allow you to mutably borrow, while one or more immutable borrow is around.

The problem is that you are trying to mutably borrow ball in edit, while it is also immutably borrowed in inspect, what is not allowed.

So while inspect is in scope, borrowing ball immutably, you will be unable to create edit.

One solution is to create/release the borrow on demand:

let inspect = |ball: &Ball| {
    println!("Unremarkable. {} ball. ", ball.color); // &ball
};
//create the immutable borrow
inspect(&ball);
//drop the immutable borrow
let edit = |ball: &mut Ball| {
    ball.color = "red".to_string(); // Not OK if i call inspect later.
};
//create the mutable borrow
edit(&mut ball)
//drop the mutable borrow
//create the immutable borrow
inspect(&ball);
//drop the immutable borrow

Upvotes: 2

Related Questions