Mutable borrow automatically changes to immutable?

2019-05-30 00:15发布

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.

标签: rust
3条回答
可以哭但决不认输i
2楼-- · 2019-05-30 00:50

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.

查看更多
放我归山
3楼-- · 2019-05-30 01:10

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.

查看更多
女痞
4楼-- · 2019-05-30 01:16

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...)
查看更多
登录 后发表回答