"Copying" mutable references

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?

2 Likes

The operation you're looking at is called a reborrow. Given a mutable reference, you can create a sub-reference (mutable or immutable), and for the duration in which the sub-reference is live, access to the original mutable reference is restricted. If the sub-reference is mutable, no access to the original reference is allowed. If the sub-reference is immutable, some immutable uses of the original reference are allowed, but nothing else.

3 Likes

Looks like reborrowing is still poorly documented...

...but one at least this Github issue is nice enough to collect a handful of links to prior discussions and explanations in forums. So feel free to take a look ^^

2 Likes

I'm in mobile and about to go to bed, but to explain just one detail here...

let cr: &String = mr;

with mr: &mut String does effectively “desugar” to something like

let cr: &String = &*mr;

I. e. the target of mr is being (immutably) borrowed, and while this borrow is active, mr (or its target) can no longer be mutated.

(More precisely, in this case what I called “desugar”ing is the implementation of the implicitly added coercion &mut String to &String. But in other cases, like with the explicit &mut String type signature in a later example, the compiler likes to insert a (that time mutable) re-borrow even without any coercions being necessary in the first place... in a sense, &mut T is a bit magical in this regard, but the magic is only that an expression foo that looks like it loves a mutable reference can sometimes implicitly become effectively &mut *foo.)

1 Like

My understanding is this:

let mut s = String::from("hello");
let mr = &mut s;
let cr: &mut String = mr;

translates into:

let mut s = String::from("hello");
let mr: &'a mut String = &mut s;
let cr: &'b mut String = mr;

Note that the two types are different because there are different lifetimes. Therefore this is not just a move, but a type coercion. The type coercion inserts a reborrow.

But if you explicitly write the code with the same lifetime, then it doesn't work (from reading the error message I think there is still technically a reborrow inserted, but that doesn't really matter):

fn foo<'a>(mr: &'a mut String) {
    let cr: &'a mut String = mr;
    println!("{cr}");
    println!("{mr}");
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.