I don't understand "where" the MutexGuard
in the inner block of code is. The mutex is locked and unwrapped, yielding a MutexGuard
. Somehow this code manages to dereference that MutexGuard
and then mutably borrow that object. Where did the MutexGuard
go? Also, confusingly, this dereference cannot be replaced with deref_mut
. Why?
use std::sync::Mutex;
fn main() {
let x = Mutex::new(Vec::new());
{
let y: &mut Vec<_> = &mut *x.lock().unwrap();
y.push(3);
println!("{:?}, {:?}", x, y);
}
let z = &mut *x.lock().unwrap();
println!("{:?}, {:?}", x, z);
}
Summary: because *x.lock().unwrap()
performs an implicit borrow of the operand x.lock().unwrap()
, the operand is treated as a place context. But since our actual operand is not a place expression, but a value expression, it gets assigned to an unnamed memory location (basically a hidden let
binding)!
See below for a more detailed explanation.
Before we dive in, first two important terms. Expressions in Rust are divided into two main categories: place expressions and value expressions.
let x = 3;
then x
is a place expression. Historically this was called lvalue expression.fn bar() -> i32
then bar()
is a value expression. Literals like 3.14
or "hi"
are value expressions too. Historically these were called rvalue expressions.There is a good rule of thumb to check if something is a place or value expression: "does it make sense to write it on the left side of an assignment?". If it does (like my_variable = ...;
) it is a place expression, if it doesn't (like 3 = ...;
) it's a value expression.
There also exist place contexts and value contexts. These are basically the "slots" in which expressions can be placed. There are only a few place contexts, which (usually, see below) require a place expression:
⟨place context⟩ = ...;
, ⟨place context⟩ += ...;
)&⟨place context⟩
and &mut ⟨place context⟩
)Note that place expressions are strictly more "powerful". They can be used in a value context without a problem, because they also represent a value.
(relevant chapter in the reference)
Let's build a small dummy example to demonstrate a thing Rust does:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
This works!
We know that the expression get_foo()
is a value expression. And we know that the operand of a borrow expression is a place context. So why does this compile? Didn't place contexts need place expressions?
Rust creates temporary let
bindings! From the reference:
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead [...].
So the above code is equivalent to:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
This is what makes your Mutex
example work: the MutexLock
is assigned to a temporary unnamed memory location! That's where it lives. Let's see:
&mut *x.lock().unwrap();
The x.lock().unwrap()
part is a value expression: it has the type MutexLock
and is returned by a function (unwrap()
) just like get_foo()
above. Then there is only one last question left: is the operand of the deref *
operator a place context? I didn't mention it in the list of place contests above...
The last piece in the puzzle are implicit borrows. From the reference:
Certain expressions will treat an expression as a place expression by implicitly borrowing it.
These include "the operand of the dereference operator (*
)"! And all operands of any implicit borrow are place contexts!
So because *x.lock().unwrap()
performs an implicit borrow, the operand x.lock().unwrap()
is a place context, but since our actual operand is not a place, but a value expression, it gets assigned to an unnamed memory location!
deref_mut()
There is an important detail of "temporary lifetimes". Let's look at the quote again:
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead [...].
Depending on the situation, Rust chooses memory locations with different lifetimes! In the &get_foo()
example above, the temporary unnamed memory location had a lifetime of the enclosing block. This is equivalent to the hidden let
binding I showed above.
However, this "temporary unnamed memory location" is not always equivalent to a let
binding! Let's take a look at this case:
fn takes_foo_ref(_: &Foo) {}
takes_foo_ref(&get_foo());
Here, the Foo
value only lives for the duration of the takes_foo_ref
call and not longer!
In general, if the reference to the temporary is used as an argument for a function call, the temporary lives only for that function call. This also includes the &self
(and &mut self
) parameter. So in get_foo().deref_mut()
, the Foo
object would also only live for the duration of deref_mut()
. But since deref_mut()
returns a reference to the Foo
object, we would get a "does not live long enough" error.
That's of course also the case for x.lock().unwrap().deref_mut()
-- that's why we get the error.
In the deref operator (*
) case, the temporary lives for the enclosing block (equivalent to a let
binding). I can only assume that this is a special case in the compiler: the compiler knows that a call to deref()
or deref_mut()
always returns a reference to the self
receiver, so it wouldn't make sense to borrow the temporary for only the function call.
Here are my thoughts:
let y: &mut Vec<_> = &mut *x.lock().unwrap();
A couple of things going on under the surface for your current code:
.lock()
yields a LockResult<MutexGuard<Vec>>
unwrap()
on the LockResult
and get a MutexGuard<Vec>
MutexGuard<T>
implements the DerefMut
interface, Rust performs deref coercion. It gets dereferenced by the *
operator, and yields a &mut Vec
. In Rust, I believe you don't call deref_mut
by your own, rather the complier will do the Deref
coercion for you.
If you want to get your MutexGuard
, you should not dereference it:
let mut y = x.lock().unwrap();
(*y).push(3);
println!("{:?}, {:?}", x, y);
//Output: Mutex { data: <locked> }, MutexGuard { lock: Mutex { data: <locked> } }
From what I have seen online, people usually do make the MutexGuard
explicit by saving it into a variable, and dereference it when it is being used, like my modified code above. I don't think there is an official pattern about this. Sometimes it will also save you from making a temporary variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With