rust 使用变量引用生成异步任务并“过早”退出根方法

rdlzhqv9  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(155)

我试图理解所有权和借用在Rust中是如何工作的。我读到过,如果你在一个作用域中定义了一个变量,然后退出这个作用域,这个变量就会被删除。假设你在一个方法中定义了一个变量,并返回一个对该变量的引用,那么这段代码将是不可编译的。这在Rust书中有解释:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

但是,在异步情况下。假设我们创建一个变量,并通过引用将其传递给派生的任务。然后我们从它的来源退出方法,这不应该删除变量吗?但是,可以构建以下代码,并且该方法是有效的:

use tokio;
use std::time::Duration;

async fn f2(x: &i32) {
    // Sleep for half a second within f2.
    tokio::time::sleep(Duration::from_millis(500)).await;

    // Attempt to read the reference to x.
    println!("[2]: f2: x = {}", x);
}

async fn f1() {
    let x = 42;

    // Spawn an asynchronous task that calls f2 with a reference to x.
    let handle = tokio::spawn(async move {
        f2(&x).await;
    });

    println!("[1]: f1: x = {}", x);

    // No waiting for the spawned task to complete.
    // f1 can exit before f2 runs to completion.
}

#[tokio::main]
async fn main() {
    f1().await;

    println!("[3]");

    // Sleep for a while to give f2 a chance to run.
    tokio::time::sleep(Duration::from_secs(1)).await;

    println!("[4]");
}

打印输出:

[1]: f1: x = 42
[3]
[2]: f2: x = 42
[4]

我希望编译器停止编译代码。但同时,我也认为编译器理解可能导致内存问题的每个异步用例有点复杂。但我至少希望代码在运行时失败,说“x”超出范围或其他。但也许它是偶然起作用的,也就是说,因为x所在的内存位置没有被擦除,但仍然包含值42?我显然是Rust的新手,无论如何我都不是一个非常有经验的程序员。

8hhllhi2

8hhllhi21#

你没有通过引用将变量x传递给新任务,你用move关键字注解了异步块,这意味着所有的自由变量(那些没有在块中定义的变量)都被移动到未来。
您仍然可以访问原始的x,因为i32实现了Copy,这意味着移动而不是使旧变量无效,只是复制数据,因此有两个版本的x,一个在任务内部,另一个在f1中。
你可以通过引用传递x来得到预期的错误,或者通过忽略move来得到这个错误:

error[E0373]: async block may outlive the current function, but it borrows `x`, which is owned by the current function
  --> src/main.rs:15:31
   |
15 |       let handle = tokio::spawn(async {
   |  _______________________________^
16 | |         f2(&x).await;
   | |             - `x` is borrowed here
17 | |     });
   | |_____^ may outlive borrowed value `x`
   |
   = note: async blocks are not executed immediately and must either take a reference or ownership of outside variables they use
help: to force the async block to take ownership of `x` (and any other referenced variables), use the `move` keyword
   |
15 |     let handle = tokio::spawn(async move {
   |                                     ++++

或者把参照物移到外面,

//…
    let x_ref = &x;
    let handle = tokio::spawn(async move {
        f2(x_ref).await;
    });
//…

导致您预期的错误:

error[E0597]: `x` does not live long enough
  --> src/main.rs:15:17
   |
12 |       let x = 42;
   |           - binding `x` declared here
...
15 |       let x_ref = &x;
   |                   ^^ borrowed value does not live long enough
16 |       let handle = tokio::spawn(async move {
   |  __________________-
17 | |         f2(x_ref).await;
18 | |     });
   | |______- argument requires that `x` is borrowed for `'static`
...
24 |   }
   |   - `x` dropped here while still borrowed

除了编译器或库错误之外,对于没有任何unsafe块的Rust代码来说,
偶然起作用,即,因为x所在的内存位置没有被擦除干净,但仍然包含值42

相关问题