Why doesn't a mutable borrow of self change to immutable?

1.4k views Asked by At

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());
}

Since non-lexical lifetimes were implemented, this is required to trigger the error:

fn main() {
    let mut dat = Data { a: 1, b: 2, c: 3 };
    let aref = dat.reference_to_a();
    let b = dat.get_b();
    println!("{:?}, {}", aref, 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

Why is this? 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?

2

There are 2 answers

5
Chris Emerson On

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.

0
Nickolay On

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: