Multiple mut borrowing of calling method

fn test(s: &mut String) {
    println!("rec: {}",*s);
    *s="bbb".to_string();
}

fn test2(s: &mut String) {
    println!("rec: {}",*s);//bbb
    *s="ccc".to_string();
}

fn main() {
  let mut s = String::from("aaa");
    test(&mut s);
    test2(&mut s);
    println!("{}", s);//ccc
}

Why the compiler does not block multiple mut borrowing of the calling method?
but i use let x =&mut s ;let y=&mut s; and then call the compiler to know that there are multiple mut.

Why would you expect different?

You can loan a book to your friend Joe, who can mutate it by writing in it, and then give it back to you.

You can then loan that same book to your other friend Mike, who can read the book, and Joe's writings, mutate it some more by writing in it, and then give it back to you.

At the end of all that you have your book back with the changes made by Joe and Mike.

Importantly, at no point in time did you give both Mike and Joe the chance to write into the book simultaneously. They took turns in borrowing it.

In your example "test()" is Joe, and "test2()" is Mike and the book is s.

If you did not intend the book to be written to do not use mut.

2 Likes

You can format your code in a readable way using:

```
// your code here
```

thank you

Not exactly ...

fn test(s: &mut String) {
    println!("rec: {}",*s);
    *s="bbb".to_string();
}

fn test2(s: &mut String) {
    println!("rec: {}",*s);//bbb
    *s="ccc".to_string();
}

fn main() {
    let mut s = String::from("aaa");
    let slice1 = &mut s;
    test(slice1);
    let slice2 = &mut s;
    test2(slice2);
    println!("{}", s);//ccc
}

Compiles just fine - because slice1 isn't being used beyond the slice2 let binding. So the version you posted is just a shorthand of this version.

2 Likes
    let slice1 = &mut s;
    let slice2 = &mut s;
    test(slice1);//found another one mut slice2
    test2(slice2);

This is my previous call.
When I call the test method, the compiler finds that there is another mut borrowing. The other methods above look more like a whole, ensuring that there is only one mut at any time.

Forgive me if you already understood this, but to make it clear again just in case, the key difference between your two pieces of code is the number of mutable references:

two mut references (bad)

let slice1 = &mut s;
let slice2 = &mut s;

one mut reference (good)

let mut s = String::from("aaa");

It's a little difficult to talk about because of the word "borrow" being used a little too widely.
Hear me out.

Does a borrow happen when making the reference let slice1 = &mut s
or when passing that reference to a function test1(slice1)?

I think the correct term is that "passing the reference to a function" is the borrow and making the reference is... something else. "Creating a reference" I suppose. I don't think there's a special term for this, it's not new to rust.

What is new to rust is the borrow-checker, and the borrow checker will enforce the rules of References

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

In other languages like C I think people had to try to enforce the rules of references themselves by just... being careful. Which is kind of terrifying, humans are really bad at this.

bonus explanantion
The term borrow is used to refer to passing a reference into a function because it contrasts with the term for passing by value, which is move. One moves a variable into a function when passing by value.

This all ties into the concept of ownership. When borrowing, the function that receives the reference doesn't own the variable, but if moving, then the function does own the variable.

I've not explained this to anyone (except myself) before, so apologies if that is a confusing mess :smiley:

Edit: I corrected my inline quotes: let slice1 = &mut s instead of let &mut s = ... and test1(slice1) instead of test1(s) woops :stuck_out_tongue:

Except that is not correct. When one passes by value and the value happens to me a integer or such it is not moved into the called function, it is copied. The caller still has the original. For example:

fn f (x: i64) {
    println!("{}", x);
}

fn main() {
    let x: i64 = 42;
    f(x);
    println!("{}", x);
}

Where both printlns work fine. That x is copied not moved.

Change that integer to a structure and it fails with a borrow error:

struct S {
    x: i64
}

fn f (s: S) {
    println!("{}", s.x);
}

fn main() {
    let s: S = S{x: 42};
    f(s);
    println!("{}", s.x);
}

Unless you implement the copy trait fro the struct. Then we are passing by value a copy again. Not a move.

This gets even more confusing for more complex things like vec where the actual stuff is allocated on the heap. The value being passed is then just the reference.

When I hear "move" I think actual memory moves and copies. As in memcpy/memmove in C.

Here in Rust land "move" has a higher level semantic meaning, our actual data values may not be copied or moving anywhere in memory. The meaning of "move" gets mixed up with movement of ownership rather than any actual data.

It's all very confusing for us newbies.

1 Like

I didn't think about Copy, that is indeed another wrinkle, good shout!

let slice1 = &mut s;  // mutable borrow #1
let slice2 = &mut s;  // mutable borrow #2 - now we are no longer allowed to use mutable borrow #1
test(slice1);         // using mutable borrow #1 AFTER mutable borrow #2 is bound - FAIL 
test2(slice2);

The reason that fails is because slice1 is used after slice2 is bound.

Change it to

let slice1 = &mut s; 
test(slice1);        // not a problem 
let slice2 = &mut s; 
test2(slice2);

and the problem goes away.

  • slice1 is no longer used after slice2 is bound
  • so the compiler can effectively pretend that slice1 no longer exists after slice2 is bound.

And it's

let slice1 = &mut s; // mutable borrow #1
test(slice1);        // last time mutable borrow #1 is used 
let slice2 = &mut s; // mutable borrow #2 
test2(slice2);       // mutable borrow #2 is used

which can then be simplified to

test(&mut s);  // use mutable borrow #1 
test2(&mut s); // use mutable borrow #2 OK - because mutable borrow #1 won't be used again

The Rust Programming Language - 4. Understanding Ownership - 4.2 References and Borrowing - Mutable References:

1 Like

These give me a deeper understanding, thank you very much!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.