Rust:如何让trait内异步函数返回的Future随参数自动实现Send?
问题背景
异步函数有个实用特性:返回的Future会在条件满足时自动实现Send。比如下面的例子,当函数输入实现Send时,异步函数生成的Future也会实现Send:
struct MyStruct; impl MyStruct { // 若T实现Send,该异步函数返回`impl Future<Output=T> + Send`;否则返回不带Send的`impl Future<Output=T>` async fn func<T>(&self, t: T) -> T { t } } fn assert_is_send(_v: impl Send) {} fn main() { // 可以正常编译 assert_is_send(MyStruct.func(4u64)); // 下面这行会编译失败,符合预期 // assert_is_send(MyStruct.func(std::rc::Rc::new(4u64))); }
现在需要把这类函数移入trait中,这时候通常要用async-trait(它会把async fn转换为返回Pin<Box<dyn Future>>的函数)或者手动实现类似逻辑。但想保留上述自动Send的行为:当参数T实现Send时,返回的Future才实现Send。目前的做法是拆分出两个独立函数:
use std::pin::Pin; use std::future::Future; struct MyStruct; impl MyStruct { fn func_send<T: 'static + Send>(&self, t: T) -> Pin<Box<dyn Future<Output = T> + Send>> { Box::pin(async{t}) } fn func_not_send<T: 'static>(&self, t: T) -> Pin<Box<dyn Future<Output = T>>> { Box::pin(async{t}) } } fn assert_is_send(_v: impl Send) {} fn main() { // 正常编译 assert_is_send(MyStruct.func_send(4u64)); // 下面这行会编译失败,符合预期 // assert_is_send(MyStruct.func_not_send(std::rc::Rc::new(4u64))); }
但不想拆分函数,希望像普通async fn一样用单个函数实现,比如类似下面的设想代码:
use std::pin::Pin; use std::future::Future; struct MyStruct; impl MyStruct { fn func<T: 'static + ?Send>(&self, t: T) -> Pin<Box<dyn Future<Output = T> + ?Send>> { Box::pin(async{t}) } } fn assert_is_send(_v: impl Send) {} fn main() { // 应该正常编译 assert_is_send(MyStruct.func(4u64)); // 下面这行应该编译失败 assert_is_send(MyStruct.func(std::rc::Rc::new(4u64))); }
这种需求在Rust中是否可行?如果手动编写async-trait的代码生成逻辑能解决,也可以接受。
尝试过的思路但没进展:
- 使用min-specialization针对Send特化:但该特性短期内不会稳定,不是最优方案。
- 返回自定义MyFuture类型而非impl Future,在T实现Send时为MyFuture实现Send:但async生成的Future类型无法命名,难度大。
- 编写过程宏,检测输入类型是否实现Send,为返回类型添加+Send:但过程宏仅处理令牌流,无法检测类型是否实现Trait,不可行。
可行解决方案
方案1:使用async-trait的?Send注解(推荐,稳定版可用)
async-trait crate支持通过#[async_trait(?Send)]注解实现和普通async fn完全一致的自动Send传播行为,无需拆分函数,且基于稳定版Rust:
use async_trait::async_trait; use std::rc::Rc; struct MyStruct; #[async_trait(?Send)] trait MyTrait { async fn func<T>(&self, t: T) -> T; } impl MyTrait for MyStruct { async fn func<T>(&self, t: T) -> T { t } } fn assert_is_send(_v: impl Send) {} fn main() { // 正常编译 assert_is_send(MyStruct.func(4u64)); // 编译失败,符合预期 // assert_is_send(MyStruct.func(Rc::new(4u64))); }
这个注解会让生成的Future在参数T实现Send时自动带上Send约束,否则不带,完全符合需求。
方案2:手动实现带条件Send的返回类型(依赖不稳定特性)
如果无法使用第三方crate,可以启用min-specialization特性,通过辅助trait实现条件返回:
#![feature(min_specialization)] use std::future::Future; use std::pin::Pin; // 辅助trait,关联不同条件下的返回Future类型 trait FuncReturn<T> { type Future: Future<Output = T>; fn make_future(t: T) -> Self::Future; } // 当T实现Send时的特化实现 impl<T: Send + 'static> FuncReturn<T> for () { type Future = Pin<Box<dyn Future<Output = T> + Send>>; fn make_future(t: T) -> Self::Future { Box::pin(async move { t }) } } // 默认实现(T不满足Send时使用) impl<T: 'static> FuncReturn<T> for () { default type Future = Pin<Box<dyn Future<Output = T>>>; default fn make_future(t: T) -> Self::Future { Box::pin(async move { t }) } } struct MyStruct; impl MyStruct { fn func<T: 'static>(&self, t: T) -> <() as FuncReturn<T>>::Future { <() as FuncReturn<T>>::make_future(t) } } fn assert_is_send(_v: impl Send) {} fn main() { // 正常编译 assert_is_send(MyStruct.func(4u64)); // 编译失败,符合预期 // assert_is_send(MyStruct.func(std::rc::Rc::new(4u64))); }
注意:该方案需要启用不稳定的min-specialization特性,仅适合可以使用 nightly 或 beta 版本Rust的场景。
方案3:函数重载(非trait场景)
如果只是在impl块中实现,不需要放入trait,可以通过函数重载实现:
use std::future::Future; struct MyStruct; impl MyStruct { // T实现Send时的版本 fn func<T>(&self, t: T) -> impl Future<Output = T> + Send where T: Send + 'static, { async move { t } } // T不实现Send时的版本 fn func<T>(&self, t: T) -> impl Future<Output = T> where T: 'static, { async move { t } } } fn assert_is_send(_v: impl Send) {} fn main() { // 正常编译 assert_is_send(MyStruct.func(4u64)); // 编译失败,符合预期 // assert_is_send(MyStruct.func(std::rc::Rc::new(4u64))); }
这种方式无需依赖第三方库或不稳定特性,但仅适用于非trait的impl场景,且编译器会根据参数是否满足Send自动选择对应重载。
总结
最实用的方案是使用async-trait的#[async_trait(?Send)]注解,它能在稳定版Rust中完美实现需求,无需拆分函数;如果无法使用第三方库,可考虑启用min-specialization的辅助trait方案,或在非trait场景下使用函数重载。
内容的提问来源于stack exchange,提问作者Heinzi

