3

I'm just a rookie in Rust and I feel a bit confused about this point. To cite an example:

fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    func(s);
}

Before discussing Rust, I wanna tell something about C++ to better clarify Rust.

So, in C++, if we pass a pointer s into a function as its parameter named s_ref, then the pointer itself will be copied as a temporary variable for the internal usage within the invoked function, and s_ref will be automatically freed when it reaches the end of scope, right?

When it comes to Rust, I think it's the same. Because reference (or pointer) is a simple data type (like i32), so the passed reference s will be generated a copy named s_ref. But if so, there're two mutable references for the same variable word, violating the defined rule of Rust.

I know I made someone mistake but I'm not able to pinpoint it. Would you please give a favor? Thanks a lot.

Eason
  • 33
  • 5

2 Answers2

9

You are close, but you are missing one key detail.

In Rust, assignment (including that of a function parameter) happens by moving the value, unless the value's type implements Copy, in which case it is copied. In C++ you have to request a move with std::move() (in the cases where it's not implicit), whereas in Rust, moving is automatic for values that cannot be copied, and a move vacates the original variable such that trying to read from it again results in a compile-time error.

The Copy trait is implemented for all shared references (&T) but it is not implemented for mutable references (&mut T).

However, the compiler does a sneaky thing in this particular case. If the the reference were moved, then s would be in a "moved from" state after the call to func() and so you would expect that you couldn't use it anymore... but you can!

func(s);
func(s);

This compiles. So what's going on?

The compiler inserts a reborrow here, as though you had written this:

func(&mut *s);

This kind of construct asks the compiler not to consume the reference s and instead reborrow its referent. (This happens implicitly when passing references to functions, as is happening here, but there are other cases where it's not implicit and you have to explicitly reborrow.)

But if so, there're two mutable references for the same variable word, violating the defined rule of Rust.

Ah, but that's not quite the rule. The rule is that, at any given moment, a value can be either:

  • Readable by any number of names, or
  • Writable by at most one name.

This doesn't mean that you can't have two mutable references to the same value, it just means that only one can be usable at a time.

In your code, main() transfers control to func() after reborrowing s. This means that s cannot be used until s_ref goes away -- but it could not possibly be used until s_ref goes away, because func() has to return before s could be used again.

So as you can see, this doesn't violate Rust's aliasing rules. When you call func(), the value word can be written to using s_ref, but it cannot be written to using s until the function returns. Then s is usable again.

We can demonstrate this with the following two programs:

fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    
    let s2: &mut str = &mut *s;
    func(s2);
    
    func(s);
}

This compiles! We reborrow s into s2, but we don't use s until we stop using s2. The compiler figures this out by itself (see non-lexical lifetimes).

However, if we interleave the usages then we have a problem:

fn func(s_ref: &mut str) {}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    let s2: &mut str = &mut *s;
    
    func(s);
    func(s2);
}

Now the compiler will complain:

error[E0499]: cannot borrow `*s` as mutable more than once at a time
 --> src/main.rs:8:10
  |
6 |     let s2: &mut str = &mut *s;
  |                        ------- first mutable borrow occurs here
7 |     
8 |     func(s);
  |          ^ second mutable borrow occurs here
9 |     func(s2);
  |          -- first borrow later used here

Between lines 6 and 9, s and s2 are both considered to be usable, and this is disallowed.

As you can see, there is no problem having multiple mutable references to the same value. However, it's not permitted for their usage to overlap. As long as they don't overlap, there isn't an issue.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Incredible! Your explanation is so clear and lucid so that even though I've not learnt reborrow mechanism yet, I still could understand more than the question itself. Thank you pretty much! – Eason Mar 23 '23 at 07:11
2

But if so, there're two mutable references for the same variable word, violating the defined rule of Rust.

It does not, because the borrows are nested, so s_ref is treated as a sub-borrow of s: as long as s_ref exists, s can't be used.

You can see this if you return s_ref from func and try to access both it and the original (or include some code which uses the returned s_ref after printing s, to make sure it doesn't get dropped):

fn func(s_ref: &mut str) -> &mut str {s_ref}

fn main() {
    let mut word = "hello".to_string();
    let s: &mut str = &mut word;
    let u = func(s);
    println!("{s} {u}"); // cannot borrow `s` as immutable because it is also borrowed as mutable
}
Masklinn
  • 34,759
  • 3
  • 38
  • 57