Summary: 因为*x.lock().unwrap()
执行一个隐性借用 https://doc.rust-lang.org/reference/expressions.html#implicit-borrows操作数的x.lock().unwrap()
,操作数被视为位置上下文。但由于我们的实际操作数不是位置表达式,而是值表达式,它被分配到一个未命名的内存位置(基本上是一个隐藏的let
捆绑)!
请参阅下文以获得更详细的解释。
位置表达式和值表达式
在我们深入讨论之前,首先介绍两个重要术语。 Rust 中的表达式分为两大类:位置表达式和值表达式。
-
地点表达表示一个有家(内存位置)的值。例如,如果您有
let x = 3;
then x
是地点表达。历史上这被称为左值表达式.
-
值表达式表示一个没有家的值(我们只能使用该值,没有与之关联的内存位置)。例如,如果您有
fn bar() -> i32
then bar()
是一个值表达式。文字如3.14
or "hi"
也是价值表达。历史上这些被称为右值表达式.
有一个很好的经验法则可以检查某物是否是位置或值表达式:“将其写在作业的左侧有意义吗?”。如果是的话(比如my_variable = ...;
)它是一个地点表达式,如果不是(比如3 = ...;
) 这是一个值表达式。
也存在地点情境 and 价值背景。这些基本上是可以放置表达式的“槽”。只有几个地点情境,这(通常,见下文)需要地点表达:
- (复合)赋值表达式的左侧 (
⟨place context⟩ = ...;
, ⟨place context⟩ += ...;
)
- 借位表达式的操作数 (
&⟨place context⟩
and &mut ⟨place context⟩
)
- ...还有更多
请注意,地点表达严格来说更“强大”。它们可以毫无问题地用在值上下文中,因为它们also代表一个值。
(参考文献中的相关章节 https://doc.rust-lang.org/reference/expressions.html#place-expressions-and-value-expressions)
暂时的生命周期
让我们构建一个小虚拟示例来演示 Rust 所做的事情:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这有效!
我们知道表达式get_foo()
is a 价值表达。我们知道借位表达式的操作数是地点背景。那么为什么会编译呢?没有地点情境 need 地点表达?
Rust 创造临时的let
绑定!从参考资料 https://doc.rust-lang.org/reference/expressions.html#temporary-lifetimes:
当在大多数地方表达式上下文中使用值表达式时,会创建一个临时的未命名内存位置并初始化为该值,并且表达式的计算结果会改为该位置[...]。
所以上面的代码相当于:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
这就是让你Mutex
示例工作:MutexLock
被分配到一个临时的未命名内存位置!那就是它居住的地方。让我们来看看:
&mut *x.lock().unwrap();
The x.lock().unwrap()
部分是一个值表达式:它具有类型MutexLock
并由函数返回(unwrap()
) 就像get_foo()
多于。那么只剩最后一个问题:deref 的操作数是*
操作地点上下文?我在上面的名次竞赛列表中没有提到它......
隐性借用
难题的最后一部分是隐式借用。从参考资料 https://doc.rust-lang.org/reference/expressions.html#implicit-borrows:
某些表达式将通过隐式借用表达式将其视为地点表达式。
其中包括“解引用运算符的操作数(*
)"!任何隐式借用的所有操作数都是位置上下文!
所以因为*x.lock().unwrap()
执行隐式借位,操作数x.lock().unwrap()
是一个位置上下文,但由于我们的实际操作数不是一个位置,而是一个值表达式,因此它被分配到一个未命名的内存位置!
为什么这不起作用deref_mut()
有一个重要的细节是“临时生命周期”。我们再看一下引用:
当在大多数地方表达式上下文中使用值表达式时,会创建一个临时的未命名内存位置并初始化为该值,并且表达式的计算结果会改为该位置[...]。
根据情况,Rust 选择不同生命周期的内存位置!在里面&get_foo()
上面的例子中,临时的未命名内存位置具有封闭块的生命周期。这相当于隐藏let
我上面展示的绑定。
However,这个“临时未命名内存位置”并不总是等同于let
捆绑!我们来看看这个案例:
fn takes_foo_ref(_: &Foo) {}
takes_foo_ref(&get_foo());
在这里,Foo
价值只在持续时间内存在takes_foo_ref
打电话,不要再长了!
一般来说,如果对临时变量的引用用作函数调用的参数,则临时变量仅在该函数调用中存在。这还包括&self
(and &mut self
) 范围。所以在get_foo().deref_mut()
, the Foo
对象也只会存活一段时间deref_mut()
。但是由于deref_mut()
返回对的引用Foo
对象,我们会得到一个“活得不够长”的错误。
这当然也是如此x.lock().unwrap().deref_mut()
-- 这就是我们收到错误的原因。
在 deref 运算符中 (*
)情况下,封闭块的临时生命周期(相当于let
捆绑)。我只能假设这是编译器中的一个特殊情况:编译器知道调用deref()
or deref_mut()
始终返回对的引用self
接收者,因此仅借用临时函数调用是没有意义的。