5

here's my problem:

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

    *s2 = String::from("world1");
    *s1 = String::from("world2");

    println!("{:?}", s);
}

it will result in a compile error because s1 has type &mut String which doesn't implement the Copy trait.

But if I change the code as below:

fn c(s: &mut String) -> &mut String {
    s
}

fn main() {
    let mut s = String::from("hello");
    let s1 = &mut s;
    let s2 = c(s1);

    *s2 = String::from("world1");
    *s1 = String::from("world2");

    println!("{:?}", s);
}

it will compile without any error message.

I know when a reference passed to a function, it means the reference borrows the value insteading of owning it.

But in the situation above, it seems like when s1 was passed to fn c and returned immediatelly, s2 borrowed s1 so s1 couldn't be derefed until s2 was out of it's lifetime scope.

So what happened when s1 was passed into the fn c?

CrayonApe
  • 383
  • 2
  • 12
  • Instead of doing `let s2 =c(s1);`, you could do `let s2 = &mut *s1;` : A reference isn't `Copy` but you can take a new reference as is done in the function. Note that this isn't an answer: I'm not clear enough regarding the rules leading to the dereference in `c`. – Denys Séguret Jun 11 '21 at 13:32
  • It *looks like* you have two mutable references to the same string, but if you add `println!("{:?}", s2);` after `*s1 = String::from("world2");` you'll get `cannot assign to *s1 because it is borrowed`. So I **guess** that this is somehow caused by NLL. Pretty interesting question though. – Svetlin Zarev Jun 11 '21 at 13:41
  • PS: The same happens if you reborrow s1 without `fn c`: `let s2 = &mut *s1;` So I guess, in the `fn c()` case, rustc is performing a reborrow under the cover, while in `s2 = s1` case it's just doing a `move`. Relevant question: https://stackoverflow.com/questions/43036156/how-can-i-reborrow-a-mutable-reference-without-passing-it-to-a-function – Svetlin Zarev Jun 11 '21 at 13:57
  • This is definitely a stumbling block for many people new to Rust. Fortunately I think Sven's answer to the duplicate is a really good explanation of why this is the case. – trent Jun 11 '21 at 14:42
  • 2
    From that answer, another way of making the function compile is to give `s2` a reference type explicitly: `let s2: &mut _ = s1;` will work fine because the compiler will infer a reborrow any time the destination is known to be a mutable reference. – trent Jun 11 '21 at 14:47
  • It's not surprising that this is a stumbling block for Rust learners, since the behaviour is both both counter-intuitive and undocumented. In my opionion, it was a mistake to introduce implicit reborrows for mutable references. – Sven Marnach Jun 11 '21 at 15:16

1 Answers1

1

From @Denys Séguret's hint, I guess when s1 was passed to fn C, Rust core compiled the parameter s1 to something like &mut *s1, so there was an immutable borrow of s1.

That's why if we put

*s2 = String::from("world1");

behind

*s1 = String::from("world2");

Rust would tell us:

assignment to borrowed `*s1`

And when s2 goes out of it's lifetime scope, there is no borrow of s1 anymore, so s1 can be derefed again.

But I'm not quite sure whether it's a right explanation.

CrayonApe
  • 383
  • 2
  • 12