How to return reference to value in Rc or RefCell

The rule when stated briefly is "memory reachable through a &mut cannot be aliased", or un-jargoned, "&mut implies unique access". That's just a core part of the language, upon which the memory safety guarantees rely. Thus violating the rule is instant undefined behavior, and since Rust doesn't have UB in safe code, if the compiler can't prove to itself your code obeys the rules, it throws an error.

The rule is sometimes phrased as "you can't have two &mut at the same time", or "you can't have a &mut and a & at the same time", but as the working examples illustrate, that phrasing is a bit too simplistic. It's really more like, "you can't have two active at the same time." This is what allows reborrowing, which is sort of like a sub-borrow:

let a = &mut owned;
let b = &mut *a;
// We're both `&mut String` but `b`'s borrow depends on `a`'s borrow.
//    We didn't move `a` to get the reborrow,
//    And the lifetime for `b` might be shorter than that of `a`
//
// `a` is unusable (inactive) while `b` is active, but not dead yet.
// Or alternatively put, using `a` again "kills" `b`'s [sub-]borrow
// (and any use of `b` thereafter emits a compiler error)
// ((And both are borrows of `owned`, so using that kills both))

These reborrows are actually all over the place: when you call a method that takes &mut, a reborrow is performed, otherwise you couldn't do something like

fn foo(v: &mut Vec<i32>) {
    v.push(0); // Vec::push takes `&mut self`
    println!("{v}");
}

Because &mut can only be moved, not copied. So you get a reborrow here whose lifetime is only as long as the call to push, and can use v again afterwards. The reborrow here is implicit; &mut *a is just the explicit way to write it. You can also reborrow only parts of the original borrow.

let v2 = &mut v[0..5];     // [] acts like a * dereference
let field = &mut object.f; // So does field access

Fine then, what exactly determines this "active or not" quality? Unfortunately, there is no formal specification. The closest thing that's been proposed so far is stacked borrows. However, stacked borrows doesn't quite cover everything Rust allows today, and will probably be expanded at some point. [1] (If you run Miri, [2] it uses a version of stacked borrows which has already evolved a bit from the paper.)

In terms of what we have discussed here,

let mut owned = String::new();
let a = &mut owned;
let b = &mut *a;
println!("{a}");

owned has a conceptual stack, creating a pushes an exclusive access based on owned, creating b pushes an exclusive access based on a, and then using a pops b (killing its borrow) so that a can be on the top of the stack again and get used. Because it got popped, b can no longer be used. Using owned would have popped everything.

Where as here

let mut owned = String::new();
let a = &mut owned;
let b = &mut owned;
println!("{a}");

In order to create b, owned must be at the top of the stack, so a gets popped and it's an error to use it afterwards. [3]

This "I'm still on the stack" property is the same thing I was trying to convey with the "I have a chain of exclusivity back to the owner" discussion. Both are mental models to reason about why you get the errors you do, by approximating the analysis the compiler actually does.


For much more technical discussion, but also in my opinion much harder to reason about one, you can read the NLL RFC. We have most of NLL today. [4] The borrow errors you get today are actually from the NLL implementation.

Or for a different approach with the same goal, you can read the Polonius [5] blog posts

Polonius handles NLL Problem Case #3, and the plan is for it to replace the current NLL implementation eventually, but it's a work in progress. [6]


  1. That's why I said Rust might grow beyond the "chain of access" model. ↩︎

  2. you can do this in the playground under Tools ↩︎

  3. Now, one could imagine this particular case being accepted -- if b is never used, or maybe b could be allowed to activate after a, etc. I.e. this is another case where Rust could evolve and break these mental models. The trick will be both getting it right (not creating soundness bugs) and not making it something too complicated for humans to reason about. ↩︎

  4. We don't have the piece that solves problem case #3 yet. ↩︎

  5. a next-generation borrow checker ↩︎

  6. You can use it on nightly with -Zpolonius. ↩︎

7 Likes