Does the mutable variable is a mutable reference in rust?

I'm going to finger out what's the difference between mutable reference and variable, so I written the following code:

fn main() {
   let mut s1 = String::from("hello");

   let r2 = &mut s1;

   s1.push_str(",world");


   println!("r3: {}", r2);
}

and got the compile error message:

can I get a summary: the mutable reference is also a mutable reference in rust except it's have ownership of the data ?

A mutable binding:

let mut s1 = String::from("hello");
//  ^^^

Lets you mutate the variable (overwrite it, create a &mut to it). But it doesn't change the type of the variable. It's more of a lint to help you not mutate things more than anything. The variable's ownership is the same with or without the mut.


A mutable reference:

let r2 = &mut s1;
//       ^^^^

Is an exclusive borrow of data that something else owns. It's a different type than the borrowed type, and a different type than a shared borrow (&).


If you move or mutate the borrowed variable, that's an exclusive action too. And as such, any references (shared or exclusive) that existed before your mutation can no longer be used afterwards.

   let r2 = &mut s1;

   // `push_str` takes a `&mut String`, which is an *exclusive*
   // borrow of `s1`...
   s1.push_str(",world");

   // ...so you can't use `r2` afterwards
   println!("r3: {}", r2);
2 Likes

I'm going to assume you meant to ask: "is the mutable binding also a mutable reference?".

To which the answer is a very definitive "no". When you write

let mut s1 = String::from("hello");

then s1 is pronouncedly not a reference. It's a String. No references in sight.

Two things might have confused you:

  1. Borrow checking doesn't only check borrows. Borrows and owning bindings interact, so both have to be checked by the compiler. If you have any sort of reference (mutable or immutable) to a value, then that value must not be moved; this is kind of obvious as it is the most fundamental thing that can invalidate a pointer.
  2. Method call syntax automatically references the receiver if needed. So while s1 is not a reference, calling s1.push_str() creates a temporary reference because String::push_str() takes &mut self.
4 Likes

I'm pretty thank you for your helpful and timely reply.

I wrote this test code originally because when reading the official Rust book at References and Borrowing - The Rust Programming Language , in the Mutable References section, it mentions a rule:

if you have a mutable reference to a value, you can have no other references to that value

After my research, this rule can be reasonably translated as:

The lifetime of an mutable reference can't overlap with the lifetime of any other reference(& and &mut) to the same variable

And the reference lifetime here is: the time from when the reference is introduced to the last time it is used.

We know that the rule is mentioned in the mutable reference section because mutable references modify the data content, and overlapping lifetimes with other references may cause data races.

When I thought of this, I realized that the above rule did not cover all cases because it only involved references and did not involve variables. for example, what happens if the variable referenced by a mutable reference attempts to modify the content during the lifetime of the mutable reference ?

Therefore, I wrote this test code:

fn main() {
   let mut s1 = String::from("hello");

   let r2 = &mut s1;

   s1.push_str(",world");

   println!("r3: {}", r2);
}

When I saw the compilation error: "second mutable borrow occurs here" at s1.push_str(",world");
I thought that s1 was also a mutable reference, and a borrow occurred here. Upon your reminder,
I checked the definition of the push_str method and found that it's first parameter is &mut self , and
only then did I realize that it created a second mutable reference to the variable s1 within the lifetime
of reference r2 , causing the compilation error.
therefore the above test code did not achieve my goal of whether it is allowed to modify the data content through the variable during the lifetime of a mutable reference.

So I tried the following test code:

fn main() {
    let mut s1 = String::from("hello");

    let r2 = &mut s1;

    s1 = String::from(",world");

    println!("r3: {}", r2);
}

It's also generated a compilation error:

error[E0506]: cannot assign to `s1` because it is borrowed
 --> src/main.rs:6:5
  |
4 |     let r2 = &mut s1;
  |              ------- borrow of `s1` occurs here
5 |
6 |     s1 = String::from(",world");
  |     ^^ assignment to borrowed `s1` occurs here
7 |
8 |     println!("r3: {}", r2);
  |                        -- borrow later used here

so it seems that during the lifetime of a mutable reference not only can there be no other references to the same variable as said in the rule, but also the data content can't be modified through the variable.

Then I wonder if it is possible to modify the data content through the variable during the lifetime of a shared reference(&), so I wrote the following test code:

fn main() {
    let mut s1 = String::from("hello");

    let r2 = &s1;

    s1 = String::from(",world");

    println!("r3: {}", r2);
}

The same compilation error occurred:

error[E0506]: cannot assign to `s1` because it is borrowed
 --> src/main.rs:6:5
  |
4 |     let r2 = &s1;
  |              --- borrow of `s1` occurs here
5 |
6 |     s1 = String::from(",world");
  |     ^^ assignment to borrowed `s1` occurs here
7 |
8 |     println!("r3: {}", r2);
  |                        -- borrow later used here

For more information about this error, try `rustc --explain E0506`.

After this, I feel that the rule could be modified to : during the lifetime of a mutable reference, there should be no other references to the same variable(both & and &mut) , and the variable's data content can't be modified through it's variable. Similarly, during the lifetime of an immutable reference, the variable's data content can't be modified through it's variable also.

the interesting term you mentioned exclusive action I guess includes: mutable borrowing , change the data content through the variable, Move (move ownership to another variable or out of the current scope)

so I guess the modified rule above can be changed again as: either borrowed references(&) or mutable references existed before the exclusive action cannot be used afterwords.

Do you think this modified rule is accurate ? thank you.

thank you for you help, yes, the second was confused me.
here is my complete train of thought: Does the mutable variable is a mutable reference in rust? - #4 by freedom

No, these two are (unfortunately) completely different features, mostly unrelated to each other.

&mut is very strict (you can't fool or bypass it), comes with guarantee of exclusive access, and applies to locations in memory.

let mut is more of a "did you mean?" lint that applies to the binding itself (the variable name) rather than actual data behind it. It's only there to express intention that you mean to reassign the variable or directly mutate it via this variable. It doesn't cover all cases of mutation, e.g. if the binding contains &mut T type, then let mut is not used.

let mut could be removed from Rust entirely and it wouldn't change anything. &mut couldn't be removed without the language falling apart.

2 Likes

Yes.

Those are also true when interpreted a certain way. There is a lot of nuance over what constitutes "other references", however.

Rust has a concept of reborrows, which is quire crucial but sadly underdocumented. Let me give a short example based on your experiments:

    let mut s1 = String::from("hello");

    let r2 = &mut s1;
    r2.push_str(", world");

    println!("r2: {}", r2);

This compiles, but if we break it down further we see:

    let mut s1 = String::from("hello");

    // First mutable borrow
    let r2 = &mut s1;
    
    // We can't have just passed in `r2` because `&mut _` do not implement
    // `Copy`, and yet we can still use `r2` later.
    //
    // Instead we passed in a *reborrow* of `*r2`, similar to:
    // ```
    // String::push_str(&mut *r2, ", world");
    // ```
    r2.push_str(", world");

    // Here we're able to use `r2` still somehow...
    println!("r2: {}", r2);

And you could reasonably ask, how is this allowed? Either we copied a &mut, but that's unsound, or this "reborrow" I mentioned is a second &mut that exists while r2 does.

The answer is that while the reborrow is usable, r2 is not usable. The reborrow only has to last for the duration of the push_str, and then it expires. After that r2 is usable again.

However as you saw, if you create a new &mut to s1 directly, that's not allowed. Creating the &mut is considered similar to moving or overwriting s1, because you can overwrite the entire value, or swap it out for a replacement value (which moves the original). Create a & to s1 directly allows you to read the entire value, so that too must not overlap with the exclusive ("mutable") r2.

So these &mut reborrows have to all nest within each other to prevent more than one &mut being "active" at the same time.

As a whole it's more accurate, yes. And overwriting, moving, or taking a &mut to the owned variable will make it not possible to use existing references afterwards. You can take a & to the owned variable and still use pre-existing shared (&) references, but not any pre-existing &mut.

The entire set of rules is quite complicated if you try to write down every single thing that is allowed. The rules are trying to balance allowing as much as possible, while still being sound and somewhat comprehensible to humans still.

But over time you'll build a mental model of how things work that will let you understand borrow errors and accepted patterns more often than not (and this thread is part of that process). In particular, having a &mut and using it for methods (reborrowing in the process) will become quite natural pretty quickly in my experience.

impl S {
    fn do_this(&mut self) {}
    fn do_that(&mut self) {}
    fn foo(&mut self) { 
        self.do_this();
        self.do_that();
    }
}

Incidentally, I recommend thinking of &mut as an exclusive reference and & a shared reference as in my opinion it makes other features of Rust's borrowing model less confusing than "mutable" and "immutable", especially when you learn more later on. [1]


  1. It may be better to not rush ahead, but I mean things things like Mutex, which can allow mutation through a shared reference (what Rust calls "interior mutability" or "shared mutability"). ↩ī¸Ž

1 Like

thank you very much, your explanation got me a deeper understanding. very helpful.