如果我从未将它分配给变量,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();

您当前代码的表面下发生了一些事情:

  1. 你的 .lock() 产生 LockResult<MutexGuard<Vec>>
  2. 您在 LockResult 上拨打了 unwrap() 并获得 MutexGuard<Vec>
  3. 因为 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 接收器的引用,所以只为函数调用借用临时文件是没有意义的。