我试图理解所有权和借用在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的新手,无论如何我都不是一个非常有经验的程序员。
1条答案
按热度按时间8hhllhi21#
你没有通过引用将变量
x
传递给新任务,你用move
关键字注解了异步块,这意味着所有的自由变量(那些没有在块中定义的变量)都被移动到未来。您仍然可以访问原始的
x
,因为i32
实现了Copy
,这意味着移动而不是使旧变量无效,只是复制数据,因此有两个版本的x
,一个在任务内部,另一个在f1
中。你可以通过引用传递
x
来得到预期的错误,或者通过忽略move
来得到这个错误:或者把参照物移到外面,
导致您预期的错误:
除了编译器或库错误之外,对于没有任何
unsafe
块的Rust代码来说,偶然起作用,即,因为x所在的内存位置没有被擦除干净,但仍然包含值42