Move out of reference via dereference operator

Hi all,

I have currently a little understanding problem regarding the dereference operator "*" in Rust.

I've learned that when x is a non-pointer type *x desugars to *std::ops::Deref::deref(&x) or *std::ops::DerefMut::deref_mut(&mut x) - depending on the context as you can read here. Both methods return an according reference: &T or &mut T.

... but when the deref methods return references, then how is it possible that you can move an inner value out of these references by unwrapping the value via the *-operator? Which is, as we know, a very common concept in Rust.

Is this some kind of special case or why and how does this work?

Regards
keks

The documentation is a bit weird, in my opinion, here. The second * is the "moving out" (by copying from a reference or moving out of a Box, which is indeed a special case).

You can't.

(Except for very special cases, eg. Copy types, or Box – which doesn't go through Deref at all.)

1 Like

I see. :slight_smile: Is eg std::sync::MutexGuard also one of these special cases? When I dereference it, it also gives me the inner value, despite it's deref implementation... should this be the case, everything makes sense again to me. :smiley:

You’re presumably basing your question about MutexGuard on some example code you tested, which you might want to share if you want a more precise description of what’s actually going on there… as for special cases, no Box is the only one, other than that you can never move out of a dereferenced value. The other case of Copy types applies more generally, depending on the target type, i.e. also for a MutexGuard for something like e.g. a Mutex<i32> (since i32 is Copy) in that you wouldn’t ever actually move the value out of such a guard, but instead, copy it.

The other possibility is that you aren’t doing a move in the first place. See e.g. the definition of the concept of “place expression context” here for some contexts, where using *foo in an expression might not actually move *foo at all. Typical examples are e.g. things like *foo < … or *foo += … operators (or of course cases where a reference is taken again, etc…)

1 Like

No it isn't. Just think about it. MutexGuard can't possibly give you its referent by-value (unless, again, the referent is trivially copiable). If it could, then what would remain in the Mutex?

You have probably used the mutex with a trivially copiable type.

Sorry I should have sent some example code earlier.^^ Here is the example that confuses me:

without deref
with deref

1 Like

I think you're being confused by implicit deref coercion; if you change print_type_of to:

fn print_type_of<T>(_: T) { 
    println!("{}", std::any::type_name::<T>())
}

then the version without the deref prints:

&mut std::sync::mutex::MutexGuard<alloc::vec::Vec<i32>>

which is the actual type of y at this point. With the deref, you get:

&mut alloc::vec::Vec<i32>

You will note that in both cases, y is an exclusive reference to something, and thus it's not been moved out.

In your version of print_type_of, Rust has applied one of the deref coercion rules for references - "From &mut T to &U when T: Deref<Target=U>" - and then you're printing the type of T, having taken &T as an argument. Rust thus strips off the & before printing the type.

2 Likes

You are never moving out anything behind any references in either example. (In the second snippet, you immediately take the address of the place you get by dereferencing.)

2 Likes

@H2CO3

That's exactly it! :slight_smile: I thought that due to the dereference operation the value gets moved out first before creating the mutable reference but it seems as if Rust is obviously smart enough to derive from the context that this isn't necessary at all! :slight_smile:

Thanks folks! Problem solved! :+1:

Expressions that you get by dereferencing a reference are places – taking their address just yields the reference. (The same is true for eg. local variables: if you take the address of a local variable, you predictably always get a reference to that variable, its value doesn't get magically moved, because it's a place.) Referencing and de-referencing are inverse operations (as their names imply).

2 Likes

Well, as far as I understand x.lock().unwrap() is a value expression in a place context here. The docs say that a temporary is usually created in such cases. My assumption was that the value gets moved into this temporary but it seems that's obviously not the case here...

as far as I understand x.lock().unwrap() is a value expression in a place context here

It is. Its value is a MutexGuard, so the MutexGuard is what is placed in the temporary in that case. At that point, the Deref impl of MutexGuard is not yet involved.

2 Likes

Ahh yes, sorry. My bad.^^

That doesn't matter. The returned lock guard has a Deref impl, and it returns a reference to the inner value. (How exactly it achieves that is nobody else's business.) Basically, the mutex guard is a smart pointer. And when dereferencing a pointer, *ptr is always, unconditionally a place. It doesn't matter where you got the pointer from. It's just an address. It doesn't matter whether address 0xf00b4r comes from a temporary or anyhwere else – the location it designates is always a place.

2 Likes

Again, this is basically impossible. If this were true, the Mutex would have no value after the guard is dropped.

(I can technically imagine degenerate mutex guard impls that do this, ie. move back and forth between themselves and the mutex, but that would violate a lot of assumptions about the value being in the mutex, which is a recipe for disaster especially in unsafe code. Not to mention that it would not generalize to RwLockGuard, since there can be more than one read guards at the same time.)

2 Likes

In case you overlooked it in @steffahn's reply, the reference summarizes what a place expression is. &*thing doesn't move out of thing any more than &thing[0] or &thing.field does.

2 Likes

Thanks for the hint! :slight_smile:

Actually I know that you can make eg an explicit reborrow with &*T which does of course not imply a move but sometimes you don't see the wood for the trees...^^

Again: As always thank you all very much for your help! :slight_smile: Now the matter is clear to me.:bulb:

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.