Different behaviors for shared and exclusive reborrowing

This code is valid, maybe because the compiler message shows that the println!("{:?}", ra); code uses only immutable borrow (ra).

#![allow(unused)]

fn main() {
    let mut ra= &mut S;
    let rval = &*ra;

    println!("{:?}", ra);
    println!("{:?}", rval);
}

#[derive(Debug)]
struct S;

However, the following code fails to compile as expected for a reborrow case:

#![allow(unused)]

fn main() {
    let mut ra= &mut S;
    let rval = &mut *ra;

    println!("{:?}", ra);
    println!("{:?}", rval);
}

#[derive(Debug)]
struct S;

And for the latter case, println!("{:?}", rval); uses mutable borrow, as stated by the compiler error message. The error message is shown below:

error[E0502]: cannot borrow `ra` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:22
  |
5 |     let rval = &mut *ra;
  |                -------- mutable borrow occurs here
6 |
7 |     println!("{:?}", ra);
  |                      ^^ immutable borrow occurs here
8 |     println!("{:?}", rval);
  |                      ---- mutable borrow later used here

So when ra and rval are both mutable borrows, why does ra use immutable borrow in the println macro while rval uses a mutable one? This leads to the behavior that is slightly different from what would be expected for a reborrow case, which should behave like a stack.

The borrow checker distinguishes between shared and exclusive borrows (and reborrows) of places, and between shared and exclusive uses. The shared use of ra[1] conflicts with *ra being exclusively borrowed,[2] but not with *ra being shared borrowed.[3]

This is the relevant part of the NLL RFC (but it's not trivial to grok IMO[4]).


  1. println!("{:?}", ra), or to remove ambiguity of macros, just &ra ↩︎

  2. let rval = &mut *ra ↩︎

  3. let rval = &*ra ↩︎

  4. and that section builds on the preceding parts of the RFC ↩︎

1 Like

I think you mean that println!("{:?}", ra) uses shared reference of ra. I agree. But using ra will also use the exclusive reference to S behind the scene. And for let rval = &*ra;, it's a shared reference to the S, so I think for the first example, there should also be a compilation error. And if for the second example, the compiler can say that println!("{:?}", rval); uses exclusive reference, then why can't println!("{:?}", ra); also use an exclusive reference?

I prefer using references directly in examples like this as macros can do arbitrary things, and println! in particular utilizes format_args! which is actually a compiler builtin. I.e. it removes a possible source of magic / special behavior.

(I don't think there's any here, but that way I don't have to even consider the possibility.)

The borrow checker considers places in such a way that these are treated differently:[1]

    let mut s = S;
    // Exclusive borrow of `s`
    let mut ra = &mut s;
    // Shared reborrow of `*ra`
    let rval = &*ra;

    // Shared use of `ra`
    &ra;
    // Shared use of `rval`
    &rval;
    let mut s = S;
    // Exclusive borrow of `s`
    let mut ra = &mut s;
    // Shared borrow of `s`
    let rval = &s;

    // Shared use of `ra`
    &ra;
    // Shared use of `rval`
    &rval;

That is, from the borrow checker's perspective, a shared borrow of s is not the same as a shared reborrow of *ra. The fact that you're going "through" ra matters.

There's a push-pull between how much the borrow checker can soundly allow and how easy it is to comprehend the rules for a human.[2] This is a case where your mental model doesn't match up with something more complicated that the borrow checker allows to support more use cases.[3]

Note that the error is talking about borrows of ra, not about what ra is borrowing (s). I take it you agree that there is no &mut ra involved with println!("{:?}", ra). The exclusive borrow which is used in println!("{:?}", rval) comes from let rval = &mut *ra.

Let me try to present an approximation of reborrowing to see if it aligns with your mental model better. I assume you have no problem with this function which just uses shared references.

fn shared_reborrow_approx<T, U>(t: &T, f: impl FnOnce(&T) -> &U) -> &U {
    let ret = f(t);
    
    // `t` and `ret` don't conflict with each other
    ret;
    t;
    ret;
    t;

    // return `ret`    
    ret
}

And here's one way you can use it with T = &mut U.

    let mut s = S;
    let ra = &mut s;
    let rval = shared_reborrow_approx(&ra, |ra| &**ra);

    // Shared use of `ra`
    &ra;
    // Shared use of `rval`
    &rval;

Only shared references to &ra are used in this snippet.[4] You can create as many copies of a & &mut T as you like and use them all interchangeably. Shared reborrows work similarly.

I guess the last piece is the closure, which has this signature:

fn<'shared, 'exclusive>(&'shared &'exclusive mut S) -> &'shared S

Do you agree that:

  • shared_borrow_approx is sensible
  • using as many & &mut _ as you want is sensible
  • the closure is sensible

If so, is there still a problem with how shared reborrows work in your mind? If not, what part do you not consider to be sensible?


  1. The let rval lines are the only difference; the first example compiles while the second example does not. ↩︎

  2. especially once you factor in unsafe that may rely on certain things not being allowed ↩︎

  3. Compare and contrast. You can't reuse s so long as the returned borrow of self.a is alive; returning &Self as well is a workaround for that which relies on shared reborrows not conflicting. This is just one example of what flexible shared reborrowing enables. ↩︎

  4. Note that the closure is not called in this snippet. ↩︎

3 Likes

Now I've read through your whole answer and I totally agree with you now. My previous misunderstanding included several aspects. Firstly, I didn't notice the

part in the compiler error message. This made me bungle together the borrowing of S and borrowing of references to S.
Secondly, the

note is very significant for me. I didn't know this fact and wonder where's the doc for it. Maybe in the nll RFC. The doc's location isn't the key part for now, anyway. It was hard for me to establish connections between this observation and the examples mentioned below. But I can at least solve these two examples separately.

For the last example, I also failed to establish a full connection with my original question. But since I think I've figured out every part of the question and your last example separately, I think it's fine.

Thank you very much for you thorough answer and dedication!

1 Like