It's basically about two things: having a non-mutable reference and a mutable reference
at the same time; what happens when a mutable reference is 'copied' (e.g. when passed to
a function).
In the following, I describe my questions and some thoughts that I have about them; I
would be really happy if someone could comment on them and correct me.
Having a non-mutable reference and a mutable reference at the same time
The following doesn't compile because of the standard rules
let mut s = String::from("hello");
let mr = &mut s;
let r = &s;
*mr = String::from("world");
However, we can do (ignore for now that we are kind of copying a &mut T here, although
&mut T is not Copy (see second question))
let mut s = String::from("hello");
let mr = &mut s;
let cr: &String = mr;
*mr = String::from("world");
but it doesn't work if we use cr after we made use of the mutability in mr
let mut s = String::from("hello");
let mr = &mut s;
let cr: &String = mr;
*mr = String::from("world");
println!("{}", cr);
So what is going on here? cr is kinda copied from mr while r directly takes the
reference to s. Where is the difference?
Let's start at the beginning:
Ignoring the borrow checker for a moment, there is no reason why the following shouldn't
be possible code, because r effectively only lives in a time area where mr is not used
as a mutable reference.
let mut s = String::from("hello");
let mr = &mut s;
let r = &s;
*mr = String::from("world");
Even something like the following should be possible
let mut s = String::from("hello");
let mr = &mut s;
let r = &s;
let _a = r.clone();
let _a = mr.clone();
let _a = r.clone();
*mr = String::from("world");
It should only be impossible if we try to use r after we made use of the mutability in
mr, i.e.,
let mut s = String::from("hello");
let mr = &mut s;
let r = &s;
*mr = String::from("world");
println!("{}", r);
shouldn't work. Let's check whether this is the case when we have cr instead of r:
let mut s = String::from("hello");
let mr = &mut s;
let cr: &String = mr;
let _a = cr.clone();
let _a = mr.clone();
let _a = cr.clone();
*mr = String::from("world");
It does compile!
So we see that with cr we have some kind of extended borrowing rules, which are that as
long as cr only lives in a time area where mr's mutability is not used, it is ok. Let's
see what happens if we define cr as &mut String:
let mut s = String::from("hello");
let mr = &mut s;
let cr: &mut String = mr;
let _a = cr.clone();
let _a = mr.clone();
// let _a = cr.clone(); // not allowed
*mr = String::from("world");
Here we cannot use mr in between. That makes sense, but the borrowing rules are still
extended.
Why is this the case with cr and not with r? To answer that, let's first think about
what we have gained by defining cr (or r) in the examples that compile (should be
possible). Effectively nothing! In every position where we used cr, we could have used
mr. In the case of r, it is clear that we can never do anything useful with r, so
instead of waiting until we want to do something 'useful' with r after the mutability
use in mr, which is not allowed, we are not allowed to define r in the first place. But
why does this not happen with cr? The answer is that cr has its use cases, probably the
most important one being function calls with references as parameters:
fn bar(cr: &String) {}
fn bar_mut(cr: &mut String) {}
let mut s = String::from("hello");
let mr = &mut s;
bar(mr);
bar_mut(mr);
// bar(&s); // error
// bar_mut(&mut s); // error
*mr = String::from("world");
We couldn't call bar(_mut) with mr if it wouldn't be possible to define a reference
(cr) during the lifetime of mr. So we see that it is necessary to have these extended
borrowing rules for cr. The following shows another example where this extension is
necessary
struct Wrapper<'t> {
some_string: &'t mut String,
}
let mut s = String::from("hello");
let cr: &String; // ok
// let i; // not allowed
// let mut i: &String = &String::from("world"); // ok
// let mut i = &String::from("world"); // ok
{
let w = Wrapper { some_string: &mut s };
cr = w.some_string;
let _a = w.some_string.clone();
}
println!("{}", cr);
I think, what the examples above have in common is that a variable declared as a
&String is allocated by a variable that is a &mut String. This brings me to my
next question.
What happens when a mutable reference is 'copied'
What happens when we pass mutable references to functions or define cr as in the
examples in the first question, i.e., what happens in
fn baz(cr: &String) {}
fn baz_mut(cr: &mut String) {}
let mut s = String::from("hello");
let mr = &mut s;
let cr: &String = mr;
bar(mr);
bar_mut(mr);
*mr = String::from("world");
with mr? It cannot be copied (since &mut T is not Copy) and it cannot be moved (because
it used later on). Is this some 'builtin magic', and if so is this how the compiler
differentiates between cr and r in the examples in the first question?