13

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 7
    [Limits of Lifetimes](https://doc.rust-lang.org/nomicon/lifetime-mismatch.html). This is exactly the same case. – aSpex Dec 16 '16 at 20:24
  • @Stargateur: I don't think the 'desugared' code is meant to compile; it is just for illustration. – Timmmm Dec 16 '16 at 20:36
  • @Stargateur: No it does not help. It is necessary to limit the lifetime of the `loan` https://play.rust-lang.org/?gist=fc564b89ff4fa44ae5463d7f407e88ca&version=stable&backtrace=0 – aSpex Dec 16 '16 at 20:38
  • @aSpex: Ah yes, that does seem to be the same. But the docs don't suggest a solution or workaround? – Timmmm Dec 16 '16 at 20:38
  • 1
    Oh indeed "This program is clearly correct according to the reference semantics we actually care about, but the lifetime system is too coarse-grained to handle that.". So the only solution is to use a independent scope. @aSpex You should post an answer. – Stargateur Dec 16 '16 at 20:41
  • I'm not good at English. So if someone wants to post an answer please feel free – aSpex Dec 16 '16 at 21:15

2 Answers2

5

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.

Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
  • 1
    Thanks, so is there a solution or workaround? Will non-lexical lifetimes fix it? – Timmmm Dec 17 '16 at 09:51
  • 3
    I don't think there's a workaround other than splitting the mutation into a separate method call. It's not a lexical lifetime thing; the `&i32` is borrowed *from* `&mut self` so they're fundamentally tied together. – Chris Emerson Dec 17 '16 at 10:17
  • 2
    @Timmmm the OP example works as-is with NLL enabled. – mcarton Aug 13 '18 at 22:17
  • 2
    _"example works as-is with NLL enabled"_ only because the `aref` is unused. If you use it, like `println!("{} {}", aref, dat.get_b());` then NLL won't be of any help, as @ChrisEmerson explained. – Nickolay May 10 '19 at 23:55
  • Is there a reason why rust couldn't be extended to allow this, or even why it would be difficult to allow this. For example, why wouldn't rust be able to "demote" the mutable borrow of self to an immutable borrow of self when returned. That way, it would work (since you can have multiple immutable borrows of the same thing). – PersonWithName Sep 24 '20 at 12:33
2

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:

Nickolay
  • 31,095
  • 13
  • 107
  • 185