如果我从未将它分配给变量,MutexGuard 在哪里?
Where is a MutexGuard if I never assign it to a variable?
我不明白 "where" 内部代码块中的 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);
}
这是我的想法:
let y: &mut Vec<_> = &mut *x.lock().unwrap();
您当前代码的表面下发生了一些事情:
- 你的
.lock()
产生 LockResult<MutexGuard<Vec>>
- 您在
LockResult
上拨打了 unwrap()
并获得 MutexGuard<Vec>
- 因为
MutexGuard<T>
实现了 DerefMut
接口,Rust 执行解引用强制。它被 *
运算符取消引用,并产生 &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
保存到变量中来显式显示,并在使用时取消引用它,就像我上面修改的代码一样。我认为没有关于此的官方模式。有时它还会使您免于创建临时变量。
总结:因为*x.lock().unwrap()
执行操作数x.lock().unwrap()
的implicit borrow,操作数被视为位置上下文。但是由于我们的实际操作数不是一个位置表达式,而是一个值表达式,它被分配到一个未命名的内存位置(基本上是一个隐藏的 let
绑定)!
请参阅下文了解更详细的说明。
放置表达式和值表达式
在我们深入之前,首先要了解两个重要术语。 Rust 中的表达式分为两大类:位置表达式和值表达式。
- 放置表达式 代表一个有家(内存位置)的值。例如,如果您有
let x = 3;
,那么 x
就是一个位置表达式。历史上这被称为 左值表达式 .
- 值表达式表示一个没有归属地的值(我们只能使用该值,没有与之关联的内存位置)。例如,如果您有
fn bar() -> i32
,则 bar()
是一个值表达式。 3.14
或 "hi"
等文字也是值表达式。历史上这些被称为 rvalue expressions.
检查某物是位置表达式还是值表达式有一个很好的经验法则:"does it make sense to write it on the left side of an assignment?"。如果是(如 my_variable = ...;
)它是一个位置表达式,如果它不是(如 3 = ...;
)它是一个值表达式。
还存在位置上下文和值上下文。这些基本上是可以放置表达式的"slots"。只有少数 place contexts,其中(通常,见下文)需要 place expression:
- (复合)赋值表达式的左侧(
⟨place context⟩ = ...;
、⟨place context⟩ += ...;
)
- 借用表达式的操作数(
&⟨place context⟩
和 &mut ⟨place context⟩
)
- ...再加几个
请注意,place 表达式更严格 "powerful"。它们可以毫无问题地用于值上下文中,因为它们 也 表示一个值。
(relevant chapter in the reference)
临时寿命
让我们构建一个小的虚拟示例来演示 Rust 所做的事情:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这有效!
我们知道表达式get_foo()
是一个值表达式。我们知道借用表达式的操作数是放置上下文。那么为什么要编译呢? 放置上下文不需要放置表达式吗?
Rust 创建临时 let
绑定!来自 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.
其中包括 "the operand of the dereference operator (*
)"!并且任何隐式借用的所有操作数都是位置上下文!
所以因为 *x.lock().unwrap()
执行隐式借用,操作数 x.lock().unwrap()
是一个位置上下文,但由于我们的实际操作数不是一个位置,而是一个值表达式,它被分配给一个未命名的内存位置!
为什么这不适用于 deref_mut()
"temporary lifetimes" 有一个重要的细节。让我们再看看这句话:
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
绑定。
但是,这个"temporary unnamed memory location"并不总是等同于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()
returns 对 Foo
对象的引用,我们会得到一个 "does not live long enough" 错误。
当然 x.lock().unwrap().deref_mut()
也是如此——这就是我们得到错误的原因。
在 deref 运算符 (*
) 的情况下,临时对象存在于封闭块中(相当于 let
绑定)。我只能假设这是编译器中的一个特例:编译器知道对 deref()
或 deref_mut()
的调用总是 returns 对 self
接收器的引用,所以只为函数调用借用临时文件是没有意义的。
我不明白 "where" 内部代码块中的 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);
}
这是我的想法:
let y: &mut Vec<_> = &mut *x.lock().unwrap();
您当前代码的表面下发生了一些事情:
- 你的
.lock()
产生LockResult<MutexGuard<Vec>>
- 您在
LockResult
上拨打了unwrap()
并获得MutexGuard<Vec>
- 因为
MutexGuard<T>
实现了DerefMut
接口,Rust 执行解引用强制。它被*
运算符取消引用,并产生&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
保存到变量中来显式显示,并在使用时取消引用它,就像我上面修改的代码一样。我认为没有关于此的官方模式。有时它还会使您免于创建临时变量。
总结:因为*x.lock().unwrap()
执行操作数x.lock().unwrap()
的implicit borrow,操作数被视为位置上下文。但是由于我们的实际操作数不是一个位置表达式,而是一个值表达式,它被分配到一个未命名的内存位置(基本上是一个隐藏的 let
绑定)!
请参阅下文了解更详细的说明。
放置表达式和值表达式
在我们深入之前,首先要了解两个重要术语。 Rust 中的表达式分为两大类:位置表达式和值表达式。
- 放置表达式 代表一个有家(内存位置)的值。例如,如果您有
let x = 3;
,那么x
就是一个位置表达式。历史上这被称为 左值表达式 . - 值表达式表示一个没有归属地的值(我们只能使用该值,没有与之关联的内存位置)。例如,如果您有
fn bar() -> i32
,则bar()
是一个值表达式。3.14
或"hi"
等文字也是值表达式。历史上这些被称为 rvalue expressions.
检查某物是位置表达式还是值表达式有一个很好的经验法则:"does it make sense to write it on the left side of an assignment?"。如果是(如 my_variable = ...;
)它是一个位置表达式,如果它不是(如 3 = ...;
)它是一个值表达式。
还存在位置上下文和值上下文。这些基本上是可以放置表达式的"slots"。只有少数 place contexts,其中(通常,见下文)需要 place expression:
- (复合)赋值表达式的左侧(
⟨place context⟩ = ...;
、⟨place context⟩ += ...;
) - 借用表达式的操作数(
&⟨place context⟩
和&mut ⟨place context⟩
) - ...再加几个
请注意,place 表达式更严格 "powerful"。它们可以毫无问题地用于值上下文中,因为它们 也 表示一个值。
(relevant chapter in the reference)
临时寿命
让我们构建一个小的虚拟示例来演示 Rust 所做的事情:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
这有效!
我们知道表达式get_foo()
是一个值表达式。我们知道借用表达式的操作数是放置上下文。那么为什么要编译呢? 放置上下文不需要放置表达式吗?
Rust 创建临时 let
绑定!来自 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.
其中包括 "the operand of the dereference operator (*
)"!并且任何隐式借用的所有操作数都是位置上下文!
所以因为 *x.lock().unwrap()
执行隐式借用,操作数 x.lock().unwrap()
是一个位置上下文,但由于我们的实际操作数不是一个位置,而是一个值表达式,它被分配给一个未命名的内存位置!
为什么这不适用于 deref_mut()
"temporary lifetimes" 有一个重要的细节。让我们再看看这句话:
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
绑定。
但是,这个"temporary unnamed memory location"并不总是等同于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()
returns 对 Foo
对象的引用,我们会得到一个 "does not live long enough" 错误。
当然 x.lock().unwrap().deref_mut()
也是如此——这就是我们得到错误的原因。
在 deref 运算符 (*
) 的情况下,临时对象存在于封闭块中(相当于 let
绑定)。我只能假设这是编译器中的一个特例:编译器知道对 deref()
或 deref_mut()
的调用总是 returns 对 self
接收器的引用,所以只为函数调用借用临时文件是没有意义的。