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

标签 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);
}

最佳答案

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

请参阅下面的详细说明。

位置表达式和值表达式

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

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

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

    还存在场所上下文和值(value)上下文。这些基本上是可以放置表达式的“插槽”。只有少数地方上下文,(通常,请参见下文)需要一个地方表达式:
  • (复合)赋值表达式(⟨place context⟩ = ...;⟨place context⟩ += ...;)的左侧
  • 借位表达式的操作数(&⟨place context⟩&mut ⟨place context⟩)
  • ...再加上一些

  • 请注意,场所表达式严格来说更“强大”。它们可以毫无问题地在值上下文中使用,因为它们也表示值。

    (relevant chapter in the reference)

    临时生命周期

    让我们构建一个小的虚拟示例来演示Rust的功能:
    struct Foo(i32);
    
    fn get_foo() -> Foo {
        Foo(0)
    }
    
    let x: &Foo = &get_foo();
    

    这行得通!

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

    Rust创建了临时的let绑定(bind)!从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.



    其中包括“取消引用运算符的操作数(*)”!而且所有隐式借用的所有操作数都是place上下文!

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

    为什么这不适用于deref_mut()
    有一个“临时生命周期”的重要细节。让我们再次看一下报价:

    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绑定(bind)。

    但是,此“临时未命名的内存位置”并不总是等同于let绑定(bind)!让我们看一下这种情况:
    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绑定(bind))。我只能假定这是编译器中的一种特殊情况:编译器知道对deref()deref_mut()的调用总是返回对self接收器的引用,因此仅借用函数调用的临时变量是没有意义的。

    关于rust - 如果我从不将MutexGuard分配给变量,它在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51335679/

    相关文章:

    c++ - 避免多个互斥锁以保护类似的竞争条件

    arrays - 传递对数组元素的引用以加速

    rust - 状态代码和 header 在 actix web 中不起作用

    linux - 使用锁定文件避免脚本的两个实例同时运行时如何避免竞争条件?

    rust - 如何在 StackVec 上实现 IntoIterator

    c++ - 多线程环境中的延迟加载数据

    rust - 当一个对象可能实现多个特征时,如何创建对特征的引用结构?

    rust - 临时别名可变引用的最佳方法是什么?

    polymorphism - 如何在向量中存储实现相同特征的不同类型并调用它们的通用函数?

    rust - 学习 Rust 和文档