我不了解内部代码块中MutexGuard
的“位置”。互斥锁被锁定和展开,产生MutexGuard
。此代码设法以某种方式取消对MutexGuard
的引用,然后可变地借用该对象。 MutexGuard
去了哪里?同样,令人困惑的是,这种取消引用不能用deref_mut
代替。为什么?
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);
}
答案 0 :(得分:6)
摘要:由于*x.lock().unwrap()
执行操作数x.lock().unwrap()
的{{3}},因此该操作数被视为位置上下文。但是由于我们的实际操作数不是位置表达式,而是值表达式,因此它被分配给未命名的存储位置(基本上是隐藏的let
绑定)!
请参阅下面的详细说明。
在深入探讨之前,请先讲两个重要术语。 Rust中的表达式主要分为两类:位置表达式和值表达式。
let x = 3;
,则x
是一个位置表达式。从历史上讲,这就是左值表达式。fn bar() -> i32
,则bar()
是一个值表达式。 3.14
或"hi"
之类的文字也是值表达式。从历史上讲,这些被称为 rvalue表达式。有一个很好的经验法则来检查某物是否是一个位置或值表达式:“将其写在作业的左侧是否有意义?”。如果是(例如my_variable = ...;
),则它是一个位置表达式;如果不是(例如3 = ...;
),则它是一个值表达式。
还存在 place上下文和 value上下文。这些基本上是可以放置表达式的“插槽”。仅有几个 place上下文,(通常,请参见下文)需要一个 place表达式 :
⟨place context⟩ = ...;
,⟨place context⟩ += ...;
)的左侧&⟨place context⟩
和&mut ⟨place context⟩
)的操作数请注意,场所表达式严格来说更“强大”。它们可以在值上下文中使用而不会出现问题,因为它们也表示值。
让我们构建一个小的虚拟示例来演示Rust所做的事情:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这行得通!
我们知道表达式get_foo()
是一个值表达式。而且我们知道借位表达式的操作数是 place context 。那么为什么要编译呢? 地方上下文是否不需要地方表达式?
Rust创建临时let
绑定!来自relevant chapter in the reference:
在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名存储位置,并初始化为该值,然后表达式求值到该位置。[...]
所以上面的代码等效于:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
这就是使您的Mutex
示例起作用的原因:MutexLock
被分配给一个临时的未命名存储位置!那就是它的住所。让我们看看:
&mut *x.lock().unwrap();
x.lock().unwrap()
部分是一个值表达式:它的类型为MutexLock
,由函数(unwrap()
)返回,就像上面的get_foo()
一样。然后只剩下最后一个问题:deref *
运算符的操作数是否是场所上下文?我没有在上面的地方比赛列表中提及它。
难题中的最后一块是隐性借用。来自the reference:
某些表达式将通过隐式借用来将其视为位置表达式。
这些包括“解引用运算符(*
)的操作数”!而且任何隐式借用的所有操作数都是场所上下文!
因此,由于*x.lock().unwrap()
执行隐式借用,因此操作数x.lock().unwrap()
是场所上下文,但是由于我们的实际操作数不是场所,而是值表达式,因此将其分配给未命名的内存位置!
deref_mut()
“临时寿命”有一个重要的细节。让我们再次看看报价:
在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名存储位置,并初始化为该值,然后表达式求值到该位置。[...]
Rust会根据情况选择具有不同生命周期的内存位置!在上面的&get_foo()
示例中,临时的未命名存储位置的寿命为封闭块的时间。这等效于我上面显示的隐藏let
绑定。
但是,此“临时未命名的内存位置”并不总是等同于let
绑定!让我们看一下这种情况:
fn takes_foo_ref(_: &Foo) {}
takes_foo_ref(&get_foo());
在这里,Foo
值仅在takes_foo_ref
通话期间有效,并且不再有效!
通常,如果将对临时引用的引用用作函数调用的参数,则临时仅存在于该函数调用中。这还包括&self
(和&mut self
)参数。因此,在get_foo().deref_mut()
中,Foo
对象也将仅在deref_mut()
的时间内存在。但是,由于deref_mut()
返回了对Foo
对象的引用,因此我们将收到“寿命不足”错误。
x.lock().unwrap().deref_mut()
当然也是如此-这就是我们得到错误的原因。
在deref运算符(*
)的情况下,封闭块的临时生存期(等效于let
绑定)。我只能假设这是编译器中的一种特殊情况:编译器知道对deref()
或deref_mut()
的调用总是返回对self
接收者的引用,因此不会只为函数调用借用临时变量是有意义的。
答案 1 :(得分:0)
这是我的想法:
let y: &mut Vec<_> = &mut *x.lock().unwrap();
当前代码在表面下发生的几件事:
.lock()
产生LockResult<MutexGuard<Vec>>
unwrap()
上致电LockResult
并获得MutexGuard<Vec>
MutexGuard<T>
实现了DerefMut
接口,因此Rust执行了deref强制。它被*
运算符取消引用,并产生一个&mut Vec
。 在Rust中,我相信您不会自己打deref_mut
,而是打the complier will do the Deref
coercion for you。
如果您想获取自己的MutexGuard
,则不应取消引用它:
let mut y = x.lock().unwrap();
(*y).push(3);
println!("{:?}, {:?}", x, y);
//Output: Mutex { data: <locked> }, MutexGuard { lock: Mutex { data: <locked> } }
根据我在网上看到的内容,人们通常会通过将MutexGuard
保存到变量中来使其显式显示,并在使用时取消引用,例如上面的修改后的代码。我认为这没有正式的模式。有时,它也可以使您免于创建临时变量。