我试图理解 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 的新手,而且无论如何我都不是一个非常有经验的程序员。
您没有通过引用将变量
x
传递给新任务,您使用 move
关键字注释了异步块,这意味着所有自由变量(块中未定义的变量)都将移至未来。
您仍然可以访问原始的
x
,因为 i32
实现了 Copy
,这意味着移动不会使旧变量无效,只是复制数据,因此 x
有两个版本,即任务内部的版本以及 f1
中的那个。
您可以通过实际通过引用传递
x
来获得预期的错误,可以通过省略 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