为何在await Future引用前需要先对其执行Pin操作?
为什么await Future的引用前必须进行Pin操作?
在使用Tokio的select!时会遇到一个规则:如果要.await一个Future的引用,被引用的Future必须被Pin住,或者实现了Unpin trait。比如下面的代码就无法编译:
let fut = example(); // example 是一个 async 函数 (&mut fut).await;
对应的编译错误信息如下:
error[E0277]: `from_generator::GenFuture<[static generator@src/main.rs:15:27: 17:2]>` cannot be unpinned ... snip ... within `impl futures::Future<Output = i32>`, the trait `Unpin` is not implemented for `from_generator::GenFuture<[static generator@src/main.rs:15:27: 17:2]>` ... snip ... note: consider using `Box::pin`
而对Future执行Pin操作后,代码就能正常编译运行:
let fut = example(); // example 是一个 async 函数 tokio::pin!(fut); (&mut fut).await;
核心原因:Future的状态机特性与内存安全
async函数生成的Future本质是状态机:当你在async块里使用.await时,Future会暂停执行,等待子任务完成;恢复时需要从暂停的位置继续,这依赖于Future内部保存的状态(比如局部变量、未完成的子任务等)。
有些Future的内部状态会包含指向自身内部数据的指针(比如async块中捕获的变量,在暂停后需要准确找到这些变量的位置)。如果Future在内存中被移动(比如被转移到另一个栈帧或堆内存),这些内部指针就会指向无效的内存地址,导致内存不安全。
Unpintrait的作用是标记一个Future可以被安全移动——实现这个trait的Future,内部不会持有指向自身的指针,移动后不会破坏状态。- 默认情况下,async函数生成的Future(由
GenFuture包装)不会自动实现Unpin,因为编译器无法保证它内部没有这类自引用指针。
当你尝试.await一个&mut Fut类型的引用时,编译器需要确保这个Future不会被移动:如果Fut没有实现Unpin,就必须通过Pin操作把它固定在内存的某个位置,禁止后续移动。这样,即使通过引用去await,Future的状态机位置不会改变,内部的指针始终有效,就能安全地恢复执行。
内容的提问来源于stack exchange,提问作者David Lichmann
相关产品推荐
相关产品推荐

