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

Ani*_*RNG 2 mutex mutability rust

我不明白MutexGuard内部代码块中的"where" .互斥锁被锁定和解开,产生一个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);
}
Run Code Online (Sandbox Code Playgroud)

Luk*_*odt 8

摘要:因为*x.lock().unwrap()执行操作数的隐式借用,操作x.lock().unwrap()数被视为位置上下文.但由于我们的实际操作数不是一个地方表达式,而是一个值表达式,它被分配给一个未命名的内存位置(基本上是一个隐藏的let绑定)!

有关更详细的说明,请参见下文.


放置表达式和值表达式

在我们潜入之前,前两个重要的术语.Rust中的表达式分为两大类:位置表达式和值表达式.

  • Place表达式表示具有home(内存位置)的值.举例来说,如果你有let x = 3;那么x一个地方的表达.从历史上看,这被称为左值表达式.
  • 值表达式表示没有主页的值(我们只能使用该值,没有与之关联的内存位置).例如,如果您有,fn bar() -> i32bar()表示值.文字也喜欢3.14或是"hi"价值表达.从历史上看,这些被称为右值表达式.

有一个很好的经验法则来检查某些东西是一个地方还是一个值表达式:"在作业的左侧写它是否有意义?".如果它(如my_variable = ...;)它是一个地方表达式,如果它不是(喜欢3 = ...;)它是一个值表达式.

还存在地方背景价值背景.这些基本上是可以放置表达式的"槽".只有少数几个位置上下文(通常见下文)需要一个位置表达式:

  • (复合)赋值表达式(?place context? = ...;,?place context? += ...;)的左侧
  • 借用表达式的操作数(&?place context?&mut ?place context?)
  • ......还有一些

请注意,位置表达式严格来说更"强大".它们可以在值上下文中使用而没有问题,因为它们代表一个值.

(参考文献中的相关章节)

临时生命

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

struct Foo(i32);

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

let x: &Foo = &get_foo();
Run Code Online (Sandbox Code Playgroud)

这有效!

我们知道表达式get_foo()是一个值表达式.我们知道借用表达式的操作数是一个位置上下文.那为什么这会编译?没有放置上下文需要放置表达式

Rust创建临时let绑定!来自参考:

在大多数场所表达式上下文中使用值表达式时,会创建一个初始化为该值的临时未命名内存位置,而表达式将计算到该位置[...].

所以上面的代码相当于:

let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
Run Code Online (Sandbox Code Playgroud)

这是使您的Mutex示例工作的原因:将MutexLock其分配给临时未命名的内存位置!那就是它的生活.让我们来看看:

&mut *x.lock().unwrap();
Run Code Online (Sandbox Code Playgroud)

x.lock().unwrap()部分是一个值表达式:它具有类型MutexLock,并由函数(unwrap())返回,get_foo()如上所述.然后只剩下最后一个问题了:deref *运算符的操作数是一个位置上下文吗?我没有在上面的地方比赛列表中提到它......

隐含借用

拼图中的最后一块是隐含的借用.来自参考:

某些表达式会通过隐式借用表达式将表达式视为地点表达式.

这些包括"取消引用运算符(*)的操作数"!所有隐含借用的操作数都是上下文!

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

为什么这不起作用 deref_mut()

"临时生命"有一个重要的细节.让我们再看一下这句话:

在大多数场所表达式上下文中使用值表达式时,会创建一个初始化为该值的临时未命名内存位置,而表达式将计算到该位置[...].

根据具体情况,Rust选择具有不同使用寿命的存储位置!在&get_foo()上面的示例中,临时未命名的内存位置具有封闭块的生命周期.这相当于let我在上面展示的隐藏绑定.

但是,这个"临时未命名的内存位置"并不总是等同于let绑定!我们来看看这个案例:

fn takes_foo_ref(_: &Foo) {}

takes_foo_ref(&get_foo());
Run Code Online (Sandbox Code Playgroud)

此处,该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接收器的引用,因此仅仅为函数调用借用临时值是没有意义的.


小智 6

以下是我的想法:

let y: &mut Vec<_> = &mut *x.lock().unwrap();
Run Code Online (Sandbox Code Playgroud)

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

  1. 你的.lock()收益是LockResult<MutexGuard<Vec>>
  2. 你叫unwrap()LockResult,并得到一个MutexGuard<Vec>
  3. 因为MutexGuard<T>实现了DerefMut接口,Rust 执行了 deref 强制。它被*运算符取消引用,并产生一个&mut Vec.

在 Rust 中,我相信您不会deref_mut自己调用,而是编译器会Deref为您进行强制

如果你想得到你的MutexGuard,你不应该取消引用它:

let mut y  = x.lock().unwrap();
(*y).push(3);
println!("{:?}, {:?}", x, y);
//Output: Mutex { data: <locked> }, MutexGuard { lock: Mutex { data: <locked> } }
Run Code Online (Sandbox Code Playgroud)

从我在网上看到的情况来看,人们通常会MutexGuard通过将其保存到变量中来使显式变得明确,并在使用时取消引用它,就像我上面修改的代码一样。我认为这没有官方模式。有时它也可以使您免于创建临时变量。