如何在Rust中存储多个Future并执行?Unpin trait报错解决
解决async trait返回Future的Unpin错误问题
背景说明
使用Rust nightly版本,通过静态分发使用原生async trait(不依赖async-traits crate),已定义如下trait及实现:
定义的Trait
pub trait MakesWidgets { async fn make_widget(&self) -> Result<Widget, String>; }
Trait实现
impl MakesWidgets for DefaultWidgetMaker { async fn make_widget(&self) -> Result<Widget, String> { self.make_widget_internal().await } }
待实现的批量方法
尝试在DefaultWidgetMaker上实现批量创建Widget的方法,逻辑是存储部分Future后续执行:
impl DefaultWidgetMaker { pub async fn make_widgets(&self, requests: Vec<bool>) -> Vec<Result<Widget, String>> { let n = requests.len(); let mut responses = Vec::with_capacity(n); for _ in 0..n { responses.push(None); } let mut pending = Vec::with_capacity(n); for _ in 0..n { pending.push(None); } for (i, should_block) in requests.iter().enumerate() { let future = self.make_widget(); if *should_block { responses[i] = Some(future.await); } else { pending[i] = Some(future); } } for (i, maybe_future) in pending.iter().enumerate() { if let Some(ref mut future) = maybe_future { responses[i] = Some(future.await); } } responses.map(|x| x.unwrap()) } }
遇到的错误
执行后出现以下编译错误:
#0 4.372 error[E0277]: `impl futures_util::Future<Output = std::result::Result<Widget, std::string::String>>` cannot be unpinned #0 4.372 --> src/xxxxxx/xxxxxxxxx.rs:65:38 #0 4.372 | #0 4.372 65 | let response = future.await; #0 4.372 | ^^^^^^ #0 4.372 | | #0 4.372 | the trait `Unpin` is not implemented for `impl futures_util::Future<Output = std::result::Result<Widget, std::string::String>>` #0 4.372 | help: remove the `.await` #0 4.372 | #0 4.372 = note: consider using `Box::pin` #0 4.372 = note: required for `&mut impl futures_util::Future<Output = std::result::Result<Widget, std::string::String>>` to implement `futures_util::Future` #0 4.372 = note: required for `&mut impl futures_util::Future<Output = std::result::Result<Widget, std::string::String>>` to implement `std::future::IntoFuture`
问题原因
async trait返回的impl Future默认未实现Unpin trait,而当你将Future存储到容器后再通过可变引用执行.await时,Rust要求该Future必须实现Unpin(避免Future在执行过程中被移动导致状态错乱)。
解决方法
方法1:用Box::pin装箱并固定Future
这是最通用的解决方案,将未立即执行的Future装箱为Pin<Box<dyn Future>>,该类型自动实现Unpin:
impl DefaultWidgetMaker { pub async fn make_widgets(&self, requests: Vec<bool>) -> Vec<Result<Widget, String>> { let n = requests.len(); let mut responses = vec![None; n]; let mut pending = vec![None; n]; for (i, &should_block) in requests.iter().enumerate() { let future = self.make_widget(); if should_block { responses[i] = Some(future.await); } else { // 装箱并固定Future,满足Unpin要求 pending[i] = Some(Box::pin(future)); } } // 遍历可变引用执行pending中的Future for (i, maybe_future) in pending.iter_mut().enumerate() { if let Some(future) = maybe_future.take() { responses[i] = Some(future.await); } } // 转换为最终结果Vec responses.into_iter().map(|x| x.unwrap()).collect() } }
方法2:给Trait的返回Future加Unpin约束
如果场景允许,可以修改Trait定义,强制返回的Future实现Unpin:
pub trait MakesWidgets { // 直接约束返回的Future为Unpin fn make_widget(&self) -> impl Future<Output = Result<Widget, String>> + Unpin; }
注意:这种方式会限制Trait的实现,因为不是所有async函数返回的Future都自动实现Unpin,仅推荐在可控场景下使用。
额外修复点
原代码还有两处小问题需要修正:
- 遍历
pending时需使用iter_mut()而非iter(),才能获取可变引用操作Future responses.map(...)错误,Vec没有map方法,需先通过into_iter()转为迭代器再用collect()生成结果
内容的提问来源于stack exchange,提问作者tacos_tacos_tacos
相关产品推荐
相关产品推荐

