将带Box的async_trait代码迁移至新async_fn_in_trait特性
迁移async_trait到Rust 1.75原生async trait的解决方案
问题背景
原有基于async_trait宏的代码如下:
#[async_trait::async_trait] pub trait SourceOld: Send { async fn get_async(&self) -> Result<String, ()>; fn clone(&self) -> Box<dyn SourceOld>; } pub fn process_old_sources(_sources: Vec<Box<dyn SourceOld>>) { todo!() }
迁移到Rust 1.75原生async trait后改写为:
pub trait SourceNew: Send { async fn get_async(&self) -> Result<String, ()>; fn clone(&self) -> Box<dyn SourceNew>; } pub fn process_new_sources(_sources: Vec<Box<dyn SourceNew>>) { todo!() }
编译时触发对象安全错误:
error[E0038]: the trait `SourceNew` cannot be made into an object --> src/lib.rs:10:28 | 10 | fn clone(&self) -> Box<dyn SourceNew>; | ^^^^^^^^^^^^^ `SourceNew` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety> --> src/lib.rs:9:14 | 7 | pub trait SourceNew: Send { | --------- this trait cannot be made into an object... 8 | // fn get_async(&self) -> impl std::future::Future<Output = Result<String, ()>> + Send; 9 | async fn get_async(&self) -> Result<String, ()>; | ^^^^^^^^^ ...because method `get_async` is `async` = help: consider moving `get_async` to another trait
编译器建议将get_async替换为fn get_async(&self) -> impl std::future::Future<Output = Result<String, ()>> + Send;,但直接替换依然无法解决对象安全问题。
错误原因
Rust 1.75的原生async trait目前不支持直接将带async方法的trait转为dyn对象。这是因为async fn在编译时会被展开为返回impl Future的方法,而impl Future是匿名关联类型,每个trait实现的具体Future类型都不同,无法统一放入虚表(vtable)中,违反了对象安全的要求。
编译器给出的impl Future建议仅适用于非动态分发的场景,无法满足Box<dyn SourceNew>的动态分发需求。
正确迁移方案
要让trait支持动态分发,需要将async方法的返回值改为类型擦除后的Future对象,具体步骤如下:
- 把
async fn替换为返回Box<dyn Future<Output = ...> + Send + '_>的普通方法,其中'_绑定到&self的生命周期,确保Future不会超出self的有效范围。 - 在实现trait时,用
Box::pin包裹async块(异步Future需要Unpin才能安全放入Box,Box::pin可自动满足这一要求)。
完整代码示例:
use std::future::Future; pub trait SourceNew: Send { // 替换async fn为返回Box<dyn Future>的普通方法 fn get_async(&self) -> Box<dyn Future<Output = Result<String, ()>> + Send + '_>; fn clone(&self) -> Box<dyn SourceNew>; } // 示例实现 #[derive(Clone)] struct MySource; impl SourceNew for MySource { fn get_async(&self) -> Box<dyn Future<Output = Result<String, ()>> + Send + '_> { // 用Box::pin包裹async块 Box::pin(async { Ok("example".to_string()) }) } fn clone(&self) -> Box<dyn SourceNew> { Box::new(self.clone()) } } pub fn process_new_sources(_sources: Vec<Box<dyn SourceNew>>) { todo!() }
补充说明
- 如果不需要动态分发(即不需要
Box<dyn SourceNew>),可以直接保留原生async fn的写法,此时编译器的impl Future建议是可行的。 - 后续Rust版本可能会优化原生async trait的对象安全支持,但当前1.75版本必须通过返回
Box<dyn Future>的方式实现动态分发。
内容的提问来源于stack exchange,提问作者Yuri Astrakhan
相关产品推荐
相关产品推荐

