You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将带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对象,具体步骤如下:

  1. 把async fn替换为返回Box<dyn Future<Output = ...> + Send + '_>的普通方法,其中'_绑定到&self的生命周期,确保Future不会超出self的有效范围。
  2. 在实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:13:25