非词法生命周期借用检查器是否会过早释放锁?

10

我已经阅读了什么是非词法生命周期。使用非词法借用检查器,以下代码可以编译:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0]; // borrows `scores`, but never used
                            // its lifetime can end here

    scores.push(4);         // borrows `scores` mutably, and succeeds
}

这种情况似乎是合理的,但是在互斥锁上,我们不希望它过早释放。

在下面的代码中,我想先锁定共享数据结构,然后执行一个闭包,主要是为了避免死锁。但是,我不确定这个锁是否会过早释放。

use lazy_static::lazy_static; // 1.3.0
use std::sync::Mutex;

struct Something;

lazy_static! {
    static ref SHARED: Mutex<Something> = Mutex::new(Something);
}

pub fn lock_and_execute(f: Box<Fn()>) {
    let _locked = SHARED.lock(); // `_locked` is never used.
                                 // does its lifetime end here?
    f();
}

Rust是否特殊处理锁,以确保它们的生命周期延伸到其作用域的结束?我们必须显式地使用该变量来避免锁的过早释放吗,就像以下代码中那样?

pub fn lock_and_execute(f: Box<Fn()>) {
    let locked = SHARED.lock(); // - lifetime begins
    f();                        // |
    drop(locked);               // - lifetime ends
}
2个回答

12
这里有一个误解: NLL(非词法生命周期)影响的是借用检查,而不是对象的实际生命周期
Rust广泛使用RAII1,因此一些对象(如锁)的Drop实现具有副作用,必须在执行流程中的确定和可预测点发生。
NLL没有改变这些对象的生命周期,因此它们的析构函数仍然正好在它们的词法范围结束时按照创建相反的顺序执行。
NLL确实改变了编译器对于生命周期的理解,以便进行借用检查。但这实际上并没有导致任何代码更改; 这仅仅是分析。该分析更加聪明,以更好地识别引用的实际使用范围:
- 在NLL之前,引用从创建到释放通常被认为处于“正在使用”状态(因此命名为lexical scope)。 - 相反,NLL:
- 尝试推迟“正在使用”跨度的开始(如果可能)。 - 最后一次使用引用结束“正在使用”跨度。在RefCell的情况下,Ref<'a>会在词法作用域结束时被丢弃,此时它将使用RefCell的引用来减少计数器。
NLL不去剥离抽象层,因此必须考虑到任何包含引用(例如Ref<'a>)的对象可能在其Drop实现中访问该引用。因此,任何包含引用的对象,如锁定,都会强制NLL将引用的“正在使用”范围延伸到它们被丢弃的时候。 1资源获取即初始化是指变量构造函数执行后,它已经获得了所需的资源,并且不处于半成品状态,一般用于表示销毁该变量将释放它拥有的任何资源。

变量遮蔽是否会立即结束变量的生命周期?(即使用 let 将相同的名称绑定到另一个对象) - Zhiyao
@ZhiyaoMa:影子变量不行,赋值语句可以。 - Matthieu M.

6
Rust是否会特殊处理锁,以确保其生命周期延伸到其作用域的结尾?
不会。这是每种类型的默认情况,并与借用检查器无关。
我们必须明确使用该变量以避免提前放弃锁吗?
不需要。
您只需要确保锁卫已绑定到变量即可。您的示例已经这样做了(`let _lock = ...`),因此锁将在作用域结束时被释放。如果您使用了`_`模式,锁将立即被释放:
您可以通过测试锁是否确实已被释放来自行证明这一点:
pub fn lock_and_execute() {
    let shared = Mutex::new(Something);

    println!("A");
    let _locked = shared.lock().unwrap();

    // If `_locked` was dropped, then we can re-lock it:
    println!("B");
    shared.lock().unwrap();

    println!("C");
}

fn main() {
    lock_and_execute();
}

这段代码会死锁,因为同一个线程尝试两次获取锁。
你也可以尝试使用需要 &mut self 的方法来检查 guard 仍然持有不可变的借用,并且它没有被释放:
pub fn lock_and_execute() {
    let mut shared = Mutex::new(Something);

    println!("A");
    let _locked = shared.lock().unwrap();

    // If `_locked` was dropped, then we can re-lock it:
    println!("B");
    shared.get_mut().unwrap();

    println!("C");
}

error[E0502]: cannot borrow `shared` as mutable because it is also borrowed as immutable
  --> src/main.rs:13:5
   |
9  |     let _locked = shared.lock().unwrap();
   |                   ------ immutable borrow occurs here
...
13 |     shared.get_mut().unwrap();
   |     ^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
16 | }
   | - immutable borrow might be used here, when `_locked` is dropped and runs the `Drop` code for type `std::sync::MutexGuard`

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接