Reputation: 897
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
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
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
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