如果我从不将MutexGuard分配给变量,那它在哪里?

时间:2018-07-14 05:50:48

标签: rust mutex mutability

我不了解内部代码块中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);
}

2 个答案:

答案 0 :(得分:6)

摘要:由于*x.lock().unwrap()执行操作数x.lock().unwrap()的{​​{3}},因此该操作数被视为位置上下文。但是由于我们的实际操作数不是位置表达式,而是值表达式,因此它被分配给未命名的存储位置(基本上是隐藏的let绑定)!

请参阅下面的详细说明。


位置表达式和值表达式

在深入探讨之前,请先讲两个重要术语。 Rust中的表达式主要分为两类:位置表达式和值表达式。

  • 位置表达式表示具有主目录(存储位置)的值。例如,如果您有let x = 3;,则x是一个位置表达式。从历史上讲,这就是左值表达式
  • 值表达式表示没有主目录的值(我们只能使用该值,没有与之关联的内存位置)。例如,如果您有fn bar() -> i32,则bar()是一个值表达式。 3.14"hi"之类的文字也是值表达式。从历史上讲,这些被称为 rvalue表达式

有一个很好的经验法则来检查某物是否是一个位置或值表达式:“将其写在作业的左侧是否有意义?”。如果是(例如my_variable = ...;),则它是一个位置表达式;如果不是(例如3 = ...;),则它是一个值表达式。

还存在 place上下文 value上下文。这些基本上是可以放置表达式的“插槽”。仅有几个 place上下文,(通常,请参见下文)需要一个 place表达式

  • (复合)赋值表达式(⟨place context⟩ = ...;⟨place context⟩ += ...;)的左侧
  • 借位表达式(&⟨place context⟩&mut ⟨place context⟩)的操作数
  • ...再加上一些

请注意,场所表达式严格来说更“强大”。它们可以在值上下文中使用而不会出现问题,因为它们表示值。

implicit borrow

临时寿命

让我们构建一个小的虚拟示例来演示Rust所做的事情:

struct Foo(i32);

fn get_foo() -> Foo {
    Foo(0)
}

let x: &Foo = &get_foo();

这行得通!

我们知道表达式get_foo()是一个值表达式。而且我们知道借位表达式的操作数是 place context 。那么为什么要编译呢? 地方上下文是否不需要地方表达式

Rust创建临时let绑定!来自relevant chapter in the reference

  

在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名存储位置,并初始化为该值,然后表达式求值到该位置。[...]

所以上面的代码等效于:

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

  

某些表达式将通过隐式借用来将其视为位置表达式。

这些包括“解引用运算符(*)的操作数”!而且任何隐式借用的所有操作数都是场所上下文!

因此,由于*x.lock().unwrap()执行隐式借用,因此操作数x.lock().unwrap()是场所上下文,但是由于我们的实际操作数不是场所,而是值表达式,因此将其分配给未命名的内存位置!

为什么不适用于deref_mut()

“临时寿命”有一个重要的细节。让我们再次看看报价:

  

在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名存储位置,并初始化为该值,然后表达式求值到该位置。[...]

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对象的引用,因此我们将收到“寿命不足”错误。

x.lock().unwrap().deref_mut()当然也是如此-这就是我们得到错误的原因。

在deref运算符(*)的情况下,封闭块的临时生存期(等效于let绑定)。我只能假设这是编译器中的一种特殊情况:编译器知道对deref()deref_mut()的调用总是返回对self接收者的引用,因此不会只为函数调用借用临时变量是有意义的。

答案 1 :(得分:0)

这是我的想法:

let y: &mut Vec<_> = &mut *x.lock().unwrap();

当前代码在表面下发生的几件事:

  1. 您的.lock()产生LockResult<MutexGuard<Vec>>
  2. 您在unwrap()上致电LockResult并获得MutexGuard<Vec>
  3. 由于MutexGuard<T>实现了DerefMut接口,因此Rust执行了deref强制。它被*运算符取消引用,并产生一个&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保存到变量中来使其显式显示,并在使用时取消引用,例如上面的修改后的代码。我认为这没有正式的模式。有时,它也可以使您免于创建临时变量。