我不明白内部代码块中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);
}
最佳答案
摘要:因为*x.lock().unwrap()
执行操作数x.lock().unwrap()
的implicit borrow,所以将操作数视为位置上下文。但是由于我们实际的操作数不是位置表达式,而是值表达式,因此它被分配给一个未命名的存储位置(基本上是一个隐藏的let
绑定(bind))!
请参阅下面的详细说明。
位置表达式和值表达式
在深入探讨之前,请先讲两个重要术语。 Rust中的表达式分为两大类:位置表达式和值表达式。
let x = 3;
,则x
是一个位置表达式。从历史上讲,这称为左值表达式。 fn bar() -> i32
,则bar()
是一个值表达式。诸如3.14
或"hi"
之类的文字也是值表达式。从历史上讲,这些被称为右值表达式。 有一个很好的经验法则来检查某物是否是一个位置或值表达式:“将其写在作业的左侧是否有意义?”。如果匹配(例如
my_variable = ...;
),则它是一个位置表达式;如果不匹配(例如3 = ...;
),则它是一个值表达式。还存在场所上下文和值(value)上下文。这些基本上是可以放置表达式的“插槽”。只有少数地方上下文,(通常,请参见下文)需要一个地方表达式:
⟨place context⟩ = ...;
,⟨place context⟩ += ...;
)的左侧&⟨place context⟩
和&mut ⟨place context⟩
)请注意,场所表达式严格来说更“强大”。它们可以毫无问题地在值上下文中使用,因为它们也表示值。
(relevant chapter in the reference)
临时生命周期
让我们构建一个小的虚拟示例来演示Rust的功能:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这行得通!
我们知道表达式
get_foo()
是一个值表达式。而且我们知道借位表达式的操作数是场所上下文。那么为什么要编译呢?场所上下文是否不需要场所表达式?Rust创建了临时的
let
绑定(bind)!从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 [...].
因此,上面的代码等效于:
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:
Certain expressions will treat an expression as a place expression by implicitly borrowing it.
其中包括“取消引用运算符的操作数(
*
)”!而且所有隐式借用的所有操作数都是place上下文!因此,由于
*x.lock().unwrap()
执行隐式借用,因此操作数x.lock().unwrap()
是场所上下文,但是由于我们的实际操作数不是场所,而是值表达式,因此将其分配给未命名的存储位置!为什么这不适用于
deref_mut()
有一个“临时生命周期”的重要细节。让我们再次看一下报价:
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 [...].
根据情况,Rust选择具有不同生命周期的内存位置!在上面的
&get_foo()
示例中,临时的未命名存储位置的生命周期为封闭块的时间。这等效于我上面显示的隐藏的let
绑定(bind)。但是,此“临时未命名的内存位置”并不总是等同于
let
绑定(bind)!让我们看一下这种情况: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
绑定(bind))。我只能假定这是编译器中的一种特殊情况:编译器知道对deref()
或deref_mut()
的调用总是返回对self
接收器的引用,因此仅借用函数调用的临时变量是没有意义的。
关于rust - 如果我从不将MutexGuard分配给变量,它在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51335679/