3

It seems that u, a mutable borrow, becomes automatically immutable in

let v = &*u;

Both u and v are then immutable borrowed references so they are both allowed.

use std::ascii::AsciiExt;

fn show(a: &str) {
    println!("a={}", a);
}

fn main() {
    let mut t = String::new();
    t.push('s');
    let u = &mut t;
    u.make_ascii_uppercase(); // u is really mutable here
    let v = &*u; // u became immutable to allow this?
    show(u); // both u and v are now accessible!
    show(v);
}

Outputs:

a=S
a=S

If I try to use u as a mutable borrow after

show(v);

compiler will recall that

let v = &*u;

is really not allowed:

cannot borrow `*u` as mutable because it is also borrowed as immutable

Is it a bug or is there really some "automatically convert mutable borrow to immutable when mutability is no longer needed" principle? I am using Rust 1.13.0.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Roman Polach
  • 447
  • 4
  • 11

3 Answers3

8

A mutable reference can be borrowed immutably, however this is not what is happening here.

When forming a reference with &, you need to be explicit about mutability; unless you specify &mut it will be an immutable reference.


Your example can be reduced to:

use std::ascii::AsciiExt;

fn main() {
    let mut t = "s".to_string();
    let u = &mut t;
    u.make_ascii_uppercase();
    let v = &*u;

    let () = v;
}

The last line is a trick to get the compiler to tell us (in the error message) what the type of v is. It reports:

error[E0308]: mismatched types
 --> <anon>:9:9
  |
9 |     let () = v;
  |         ^^ expected reference, found ()
  |
  = note: expected type `&std::string::String`
  = note:    found type `()`

Here we have:

  • u: an immutable binding, which is a mutable borrow of t
  • v: an immutable binding, which is an immutable re-borrow of t through u

If, however, I change the v line to let v = &mut *u;, then I get expected type '&mut std::string::String' and then we have:

  • u: an immutable binding, which is a mutable borrow of t
  • v: an immutable binding, which is a mutable re-borrow of t through u

The important concept here is re-borrowing, which is what &*u and &mut *u are about. Re-borrowing allows forming a new reference from an existing reference:

  • a re-borrow access the initially borrowed variable
  • for the lifetime of the re-borrow, the reference from which it is formed is borrowed

The re-borrowing rules are relatively simple, they mirror the borrowing rules:

  • if you start from an immutable reference:
    • you can re-borrow it only as an immutable reference, with multiple concurrent immutable re-borrow if you wish
  • if you start from a mutable reference:
    • you can either re-borrow it as a mutable reference, exclusively
    • or you can re-borrow it as an immutable reference, with multiple concurrent immutable re-borrow if you wish

It is interesting to note that a re-borrowed reference can live longer than the reference it was formed from:

fn main() {
    let mut t = "s".to_string();

    let v;
    {
        let u = &mut t;
        v = &mut *u;
    }

    v.make_ascii_uppercase();
    show(v);
}

This is necessary to ensure that you can return a reference from functions; of course.

So, ultimately, a re-borrow is tracked down to the original borrowed value by the compiler; however, due the re-borrowing mechanics it allows forming an immutable reference to this original value even though a mutable reference is in scope... and simply make sure that this mutable reference is unusable for the lifetime of the new immutable reference.


When a function takes a reference, the compiler automatically introduces a re-borrow at the call site with the appropriate mutability; this is what happens with show here: show(u) really is show(&*u) with a new immutable reference formed for the duration of the function call.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I still does not understand. By calling _u.make_ascii_uppercase();_ I am sure that the borrow is mutable, or compiler would complain otherwise. – Roman Polach Nov 17 '16 at 12:52
  • @RomanPolach: There are two different things here. (1) `u` borrows `t` mutably and (2) `make_ascii_uppercase` borrows the referee of `u` mutably. (1) lasts until the end of the function, however (2) only lasts for the duration of the function call (no return value to extend it). Then, in the lines `show(u)` you have (3) `show` borrowing the referee of `u` immutably for the duration of the function call. – Matthieu M. Nov 17 '16 at 12:58
  • So _u_ is muttable borrow before calling _make_ascii_uppercase_ and then it is an immutable borrow after that because it is _used_ as immutable? That is the thing I am curious about. – Roman Polach Nov 17 '16 at 13:03
  • 1
    I'm not sure I get this either. Are you saying that `u`, despite being an `&mut` reference to `t`, doesn't actually borrow `t` until passed to a function which makes use of it? – Chris Emerson Nov 17 '16 at 13:23
  • @RomanPolach: `u` is an immutable binding, its value cannot change (it cannot refer to another variable), whose value is a mutable reference to `t`. The type of `u` does not change, it is *always* a mutable reference. However, one can borrow from `u` (forming a new reference), and this new reference may be either mutable or immutable. – Matthieu M. Nov 17 '16 at 13:28
  • @ChrisEmerson: Uh... no. Or if I do, it's certainly accidental... Am I that unclear? – Matthieu M. Nov 17 '16 at 13:30
  • When I made `u` a mutable reference, then making another reference (`v`) should be disallowed. But the code compiles. That is I do not understand. – Roman Polach Nov 17 '16 at 13:33
  • Moreover, if I add a new call to the end of `main` function that _uses_ `u` as mutable (like second call `u.make_ascii_uppercase();`), then the compiler recall that `let v = &*u;` is really illegal. But why it didn't complain before? – Roman Polach Nov 17 '16 at 13:35
  • @RomanPolach: I reviewed the answer, to explain how I understand this. I must admit it's quite difficult for me as I find the behavior intuitive and never really attempted an in-depth explanation of how it worked, so please tell me if there are still portions of the answer that are unclear. – Matthieu M. Nov 17 '16 at 13:49
  • The section on re-borrowing makes it clear for me, thanks. – Chris Emerson Nov 17 '16 at 13:53
  • Thanks a lot, your reviewed answer makes me understand now. I was unaware of re-borrowing mechanism before, though I suspected something like that (but simpler) in transforming real `&mut str` parameter in place of formal `&str` function parameter. But The compiler cleverness making side-effect to `u` when binding `v` was beyond my imagination. – Roman Polach Nov 17 '16 at 14:23
1

This is confusing, so let's do some experiments.

Your code compiles:

let mut t = String::new();
t.push('s');
let u = &mut t;
u.make_ascii_uppercase(); // u is really mutable here
let v = &*u; // u became immutable to allow this?
show(u); // both u and v are now accessible!
show(v);

What happens if we change the let v line to:

let v = &t;

error[E0502]: cannot borrow t as immutable because it is also borrowed as mutable

--> :12:14

Ok, so that's different. That tells me that &*u, despite being the same type as &t, is not the same; the former is (sub-)borrowing from u, but the latter is trying to reborrow t.

Let's try a different experiment. Putting the previous line back, but now adding something new:

let v = &*u;   // the original reborrow
let w = u;     // Try to move out of `u`

error[E0502]: cannot borrow t as immutable because it is also borrowed as mutable

--> :12:14

Aha! That confirms that v really is borrowing from u rather than directly from t.

Now, in the original, let's add an attempted mutation via u to the end:

let mut t = String::new();
t.push('s');
let u = &mut t;
u.make_ascii_uppercase(); // u is really mutable here
let v = &*u; // u became immutable to allow this?
show(u); // both u and v are now accessible!
show(v);
u.make_ascii_uppercase();

Now I get:

error[E0502]: cannot borrow *u as mutable because it is also borrowed as immutable

I think that basically explains what's going on:

  • u borrows t mutably. This stops t being accessed at all directly.
  • v borrows u immutably. This means that u can still be used immutably, but it can't be used mutably or moved out of.
  • The other key thing is that you can only use mutable values if the full path to the item is mutable. Since u can't be borrowed mutably while v exists, you can't use *u mutably either. (This last bit is slightly handwavey; I'd welcome further clarifications...)
Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
  • My take is that `u` is itself borrowed while `v` exists, and thus must obey the same rules as normal: if `v` is a mutable borrow, `u` cannot be re-borrowed at all, if `v` is an immutable borrow, then only other immutable borrows can be formed. – Matthieu M. Nov 17 '16 at 13:51
  • The most confusing is the side effect to `u` when binding `v`. In the first code example compiler check that `u` is not used mutably after `&*u` and allows taking mutable reference `v`. In the last code example it knows that `u` is really used as mutable reference, so it disallows `&*u`... – Roman Polach Nov 17 '16 at 14:36
0

First of all, u is not mutable at any point, as it was declared with let u, not let mut u. The reason why you can mutate the String it points to is that it holds a mutable reference to it; make_ascii_uppercase() modifies t.

v is also immutable (no mut in let v), so when you call show() that works on immutable references, the borrowing rules are not violated - you can perform multiple immutable borrows at once.

ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • Yes, by muttable and immutable I always mean the borrow/reference, not the variable binding. The problem I am trying to describe is that I made mutable borrow (u) and that borrow became an immutable borrow in some circumstances (when used only like a immutable borrow). – Roman Polach Nov 17 '16 at 12:46