简介:因为*x.lock().unwrap()
对操作数x.lock().unwrap()
进行了隐式借用,所以该操作数被视为是一个位置上下文。但是,由于我们实际的操作数不是一个位置表达式,而是一个值表达式,它被赋给了一个未命名的内存位置(基本上是一个隐藏的let
绑定)!
请参见下面的更详细的解释。
位置表达式和值表达式
在我们深入研究之前,首先了解两个重要的术语。在Rust中,表达式分为两大类:位置表达式和值表达式。
- 位置表达式表示一个有家(内存位置)的值。例如,如果你有
let x = 3;
那么x
就是一个位置表达式。历史上,这被称为左值表达式。
- 值表达式表示一个没有家的值(我们只能使用该值,它没有与之关联的内存位置)。例如,如果你有
fn bar() -> i32
,那么bar()
就是一个值表达式。像3.14
或"hi"
这样的文字也是值表达式。历史上,这些被称为右值表达式。
有一个很好的经验法则来检查某个东西是位置还是值表达式:“它是否有意义将其写在赋值符号的左边?”如果有(比如my_variable = ...;
),那么它就是一个位置表达式;如果没有(比如3 = ...;
),那么它就是一个值表达式。
还有位置上下文和值上下文。这些基本上是表达式可以放置的“插槽”。只有很少的位置上下文,它们(通常情况下,请见下文)需要一个位置表达式:
- 复合赋值表达式的左侧 (
⟨place context⟩ = ...;
, ⟨place context⟩ += ...;
)
- borrow 表达式的操作数 (
&⟨place context⟩
和 &mut ⟨place context⟩
)
- ... 还有更多
请注意,place 表达式比较 "强大"。它们可以在值上下文中使用而不会出现问题,因为它们还表示一个值。
(参考手册中相关章节)
临时生命周期
让我们构建一个小的虚拟示例来演示 Rust 的一件事情:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这很有效!
我们知道表达式get_foo()
是一个值表达式。我们也知道借用表达式的操作数是一个位置上下文。那么为什么这个代码可以编译?难道不需要位置表达式吗?
Rust会创建临时的let
绑定!来自the reference:
在大多数位置表达式上下文中使用值表达式时,将创建一个未命名的临时内存位置,该位置初始化为该值,并将表达式评估为该位置[...]。
因此,上面的代码等同于:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
这就是使你的Mutex
示例工作的原因:将MutexLock
分配给一个临时的无名内存位置!它就住在那里。让我们看一下:
&mut *x.lock().unwrap();
x.lock().unwrap()
部分是一个值表达式:它具有类型
MutexLock
,并像上面的
get_foo()
一样由函数(
unwrap()
)返回。然后只剩下一个问题:解引用运算符
*
的操作数是否为place context?我没有在上面的place contests列表中提到它...
隐式借用
拼图中的最后一块是隐式借用。来自the reference:
某些表达式将通过隐式借用将表达式视为place expression。
这些包括"解引用运算符(*
)的操作数"!任何隐式借用的所有操作数都是place contexts!
因此,因为*x.lock().unwrap()
执行了隐式借用,操作数x.lock().unwrap()
是一个place context,但由于我们的实际操作数不是一个place,而是一个值表达式,所以它被赋值给一个未命名的内存位置!
为什么对于deref_mut()
不起作用
有一个重要的细节是"temporary lifetimes"。让我们再看一下引用:
在大多数place expression上下文中使用值表达式时,将创建一个临时的未命名内存位置,并将其初始化为该值,表达式将计算为该位置[...]。
根据情况,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
对象的引用,因此我们将得到“ does not live long enough”错误。
当然,对于
x.lock().unwrap().deref_mut()
也是如此 - 这就是为什么我们会收到错误消息的原因。
在解引用操作符(
*
)的情况下,临时对象存在于封闭块(等效于
let
绑定)中。我只能假设这是编译器中的特殊情况:编译器知道对
deref()
或
deref_mut()
的调用总是返回对
self
接收器的引用,因此仅将临时对象借用给函数调用没有意义。