Mutable references over dereferences


#1

Hello!

New to Rust and so “another one dumb question”. What I miss here? :

let x =  &mut 0;
let y = &mut *x;
let z = &mut *y;

All three variables have the type of &mut i32 and definitely pointing to the ‘zero’. And of course I can only mutate it through ‘z’ at this point. But how last two constructions are possible at all? What is dereference actually in terms of type system? Why can I take the (mutable) reference over dereferenced value … already (mutable) referenced early?

I feel myself like in a vicious circle here ))
Thanks in advance!


#2

You can write &mut *x the same way you can write &mut x.field (which, unlike x.field, won’t attempt to move the field) and &mut x[0] (which won’t attempt to move x[0]), and heck, even &mut x (which won’t attempt to move x).

And really, it is &mut *x that is a complete, semantic unit of code, not *x. To see this, observe the following:

let mut vec = vec![0i32];
let borrow = &mut vec;
let borrow = &mut *borrow; // okay; this doesn't try to move the vec
let borrow = &mut {*borrow}; // Error: attempt to move out of borrowed content

x, *x, x[0], and x.field are just kind of special like this. To use a C++ term, they are “lvalues”; but I don’t know if there’s a term for them in rust. This is in contrast to rvalues (i.e. temporaries) which is what you get from calling a function (as well as most arbitrary expressions, such as {*borrow} above).


#3

I feel comfortable with lvalues/rvalues and their contexts. And I perfectly understand that dereference op produces lvalue. But I think that trick is here:

My logical loop is:

  • we take a pointer (reference) and dereference it (*x)
  • then we apply a borrow op to produced value (&mut)
  • BUT the value in step 2 has been already borrowed(!) (has the reference over it) mind-block

I’m fighting about the fact that we cannot have two mutable references over the same value. And it seems that 0 and *x are not the same values for the compiler in such semantic unit of code :)?


#4

I think I see what you’re saying.

This is a good question, and I don’t entirely know the answer. But I believe that mutability analysis in rust is designed to be done locally.

What I mean by “locally” is that that 0, *x, *y, and *z are all considered to be separate entities for the purposes of borrow checking. The fact that they are ultimately all the same thing doesn’t really matter, because the local rules are strong enough to guarantee that one cannot construct a situation where the 0 is aliased in any manner that’s actually dangerous. (note: I don’t know how to prove this myself)

So as far as the checker is concerned, y here really just borrows from x, not the 0. And z borrows from y. And in that respect, no thing is ever borrowed from twice simultaneously.


#5

Thank You!

I came to the same conclusion and simply didn’t know how to prove this myself :).And while we are not corrected by opponents, I will stick to this version.


#6

The way I see it that dereferencing/moving is done lazily, so temporarily a dereference can exist if you don’t actually use it.

Similarly match *x {ref x => {}} works, even if you’re not allowed to use *x by itself.


#7

Those last 2 are called reborrows - it’s mentioned in the book. This is also how mutable borrows are passed across functions. The net effect is that when you reborrow, the original borrow is not live until the reborrow ends.


#8

@vitalyd, You saved my day!

It is interesting to note that I met reborrow term occasionally. But I thought that it means a simple move of &mut. And I could have corrected myself if the documentation were not constantly changing. The chapter about Liveness have gone now completely. And this is one of the curses in learning curve of Rust. Things are changed too often. And some useful info dropped to please the masses.

And so for poor souls like me - yes, &mut *x is a special. It means reference reborrowing. You can easily find some useful references on this subject by Google (Liveness and this one to mention)


#9

Yeah, I recall seeing it mentioned in the first edition of the book but I’ve not looked at the second edition in depth (from a cursory glance it looks to have been drastically reorganized from the first). My recollection is that it’s mentioned somewhat briefly in the first edition too, so it would’ve been easy to somewhat miss it.

It’s true - everything about Rust is evolving/changing quite rapidly at the moment - language, core libs, crates ecosystem, docs, etc. I can highly recommend the O’Reilly Programming Rust book. It itself has undergone several revisions but it’s a more thorough treatment than the Rust book. Of course it may get stale soon due to the changes but it covers the groundwork fairly well.