Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutable borrow automatically changes to immutable?

Tags:

rust

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.

like image 766
Roman Polach Avatar asked May 14 '26 06:05

Roman Polach


2 Answers

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.

like image 141
Matthieu M. Avatar answered May 15 '26 18:05

Matthieu M.


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...)
like image 31
Chris Emerson Avatar answered May 15 '26 18:05

Chris Emerson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!