This code fails the dreaded borrow checker (playground):
struct Data {
a: i32,
b: i32,
c: i32,
}
impl Data {
fn reference_to_a(&mut self) -> &i32 {
self.c = 1;
&self.a
}
fn get_b(&self) -> i32 {
self.b
}
}
fn main() {
let mut dat = Data{ a: 1, b: 2, c: 3 };
let aref = dat.reference_to_a();
println!("{}", dat.get_b());
}
Error:
error[E0502]: cannot borrow `dat` as immutable because it is also borrowed as mutable
--> <anon>:19:20
|
18 | let aref = dat.reference_to_a();
| --- mutable borrow occurs here
19 | println!("{}", dat.get_b());
| ^^^ immutable borrow occurs here
20 | }
| - mutable borrow ends here
Can someone explain exactly why this is? I would have thought that the mutable borrow of dat
is converted into an immutable one when reference_to_a()
returns, because that function only returns an immutable reference. Is the borrow checker just not clever enough yet? Is this planned? Is there a way around it?
Lifetimes are separate from whether a reference is mutable or not. Working through the code:
fn reference_to_a(&mut self) -> &i32
Although the lifetimes have been elided, this is equivalent to:
fn reference_to_a<'a>(&'a mut self) -> &'a i32
i.e. the input and output lifetimes are the same. That's the only way to assign lifetimes to a function like this (unless it returned an &'static
reference to global data), since you can't make up the output lifetime from nothing.
That means that if you keep the return value alive by saving it in a variable, you're keeping the &mut self
alive too.
Another way of thinking about it is that the &i32
is a sub-borrow of &mut self
, so is only valid until that expires.
As @aSpex points out, this is covered in the nomicon.
Why is this an error: While a more precise explanation was already given by @Chris some 2.5 years ago, you can read fn reference_to_a(&mut self) -> &i32
as a declaration that:
“I want to exclusively borrow self
, then return a shared/immutable reference which lives as long as the original exclusive borrow” (source)
Apparently it can even prevent me from shooting myself in the foot.
Is the borrow checker just not clever enough yet? Is this planned?
There's still no way to express "I want to exclusively borrow self for the duration of the call, and return a shared reference with a separate lifetime". It is mentioned in the nomicon as @aSpex pointed out, and is listed among the Things Rust doesn’t let you do as of late 2018.
I couldn't find specific plans to tackle this, as previously other borrow checker improvements were deemed higher priority. The idea about allowing separate read/write "lifetime roles" (Ref2<'r, 'w>
) was mentioned in the NLL RFC, but no-one has made it into an RFC of its own, as far as I can see.
Is there a way around it? Not really, but depending on the reason you needed this in the first place, other ways of structuring the code may be appropriate:
- You can return a copy/clone instead of the reference
- Sometimes you can split a
fn(&mut self) -> &T
into two, one taking &mut self
and another returning &T
, as suggested by @Chris here
- As is often the case in Rust, rearranging your structs to be "data-oriented" rather than "object-oriented" can help
- You can return a shared reference from the method:
fn(&mut self) -> (&Self, &T)
(from this answer)
- You can make the fn take a shared
&self
reference and use interior mutability (i.e. define the parts of Self
that need to be mutated as Cell<T>
or RefCell<T>
). This may feel like cheating, but it's actually appropriate, e.g. when the reason you need mutability as an implementation detail of a logically-immutable method. After all we're making a method take a &mut self
not because it mutates parts of self
, but to make it known to the caller so that it's possible to reason about which values can change in a complex program.