Rust中trait约束下Future不满足Send trait的编译问题
问题:Axum处理器中使用泛型Trait约束导致Future非Send错误
我有一个JobManager类型,为了通过automock测试依赖对该模块的调用,将核心功能封装到GetOrCreate trait中。直接传递Arc<JobManager>给Axum处理器时代码可正常编译,但换成泛型Trait约束<T: GetOrCreate>后,出现future returned by ... is not Send的编译错误,即使给所有相关类型添加Send + Sync + 'static约束也无法解决。
代码概要
use mockall::automock; use tokio::sync::Mutex; use std::collections::HashMap; use thiserror::Error; use axum::{Router, routing::get, extract::{State, Path}}; use serde::Deserialize; use serde_with::{serde_as, hex::Hex}; use std::sync::Arc; pub struct JobManager { jobs: Mutex<HashMap<Job, JobState>>, } #[derive(Clone, Error, Debug, PartialEq)] pub enum JobError { #[error("Job creation failed")] CreationFailed, } #[automock] pub trait GetOrCreate: Send + Sync + 'static { async fn get_or_create(&self, job: Job) -> Result<JobState, JobError>; } impl GetOrCreate for JobManager { async fn get_or_create(&self, job: Job) -> Result<JobState, JobError> { let mut hs = self.jobs.lock().await; match hs.get(&job).cloned() { Some(j) => Ok(j), None => { hs.insert(job.clone(), JobState::Created); Ok(JobState::Created) } } } } impl JobManager { pub fn new() -> Self { JobManager { jobs: Mutex::new(HashMap::new()), } } } #[derive(Clone, Debug, PartialEq)] pub enum JobState { Created, InProgress, Done(Result<u64, String>), } #[derive(Hash, Eq, PartialEq, Debug, Clone)] pub struct Job { pub nonce_group: u8, pub challenge: [u8; 8], pub difficulty: [u8; 32], pub miner: [u8; 32], } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let job_manager = JobManager::new(); let router = router(Arc::new(job_manager)); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); axum::serve(listener, router).await?; Ok(()) } fn router<T: GetOrCreate + Send + Sync + 'static>(job_manager: Arc<T>) -> Router { Router::new() .route( "/job/:nonce_group/:challenge/:difficulty/:miner", get(get_job), ) .with_state(job_manager) } #[serde_as] #[derive(Deserialize)] struct HexStr<const COUNT: usize>(#[serde_as(as = "Hex")] [u8; COUNT]); impl<const COUNT: usize> HexStr<COUNT> { fn as_array(self) -> [u8; COUNT] { self.0 } } async fn get_job<T: GetOrCreate + Send + Sync + 'static>( State(manager): State<Arc<T>>, Path((nonce_group, challenge, difficulty, miner)): Path<( u8, HexStr<8>, HexStr<32>, HexStr<32>, )>, ) -> Result<JobState, JobError> { manager .get_or_create(Job { nonce_group, challenge: challenge.as_array(), difficulty: difficulty.as_array(), miner: miner.as_array(), }) .await }
编译错误信息
error: future cannot be sent between threads safely --> src/main.rs:138:17 | 138 | get(get_job), | ^^^^^^^ future returned by `get_job` is not `Send` | = help: within `impl Future<Output = Result<JobState, JobError>>`, the trait `Send` is not implemented for `impl Future<Output = Result<JobState, JobError>>`, which is required by `fn(State<Arc<_>>, axum::extract::Path<(u8, HexStr<8>, HexStr<32>, HexStr<32>)>) -> impl Future<Output = Result<JobState, JobError>> {get_job::<_>}: Handler<_, _>` note: future is not `Send` as it awaits another future which is not `Send` --> src/main.rs:196:5 | 196 | / manager 197 | | .get_or_create(Job { 198 | | nonce_group, 199 | | challenge: challenge.as_array().clone(), 200 | | difficulty: difficulty.as_array().clone(), 201 | | miner: miner.as_array().clone(), 202 | | }) | |__________^ await occurs here on type `impl Future<Output = Result<JobState, JobError>>`, which is not `Send` note: required by a bound in `axum::routing::get` --> /home/acud/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.5/src/routing/method_routing.rs:385:1 | 385 | top_level_handler_fn!(get, GET); | ^^^^^^^^^^^^^^^^^^^^^^---^^^^^^ | | | | | required by a bound in this function | required by this bound in `get` = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) help: `Send` can be made part of the associated future's guarantees for all implementations of `GetOrCreate::get_or_create` | 41 - async fn get_or_create(&self, job: Job) -> Result<JobState, JobError>; 41 + fn get_or_create(&self, job: Job) -> impl std::future::Future<Output = Result<JobState, JobError>> + Send; |
解决方法
根据编译器提示,修改GetOrCreate trait的方法定义,将异步方法显式声明为返回带Send约束的Future:
1. 修改Trait定义
#[automock] pub trait GetOrCreate: Send + Sync + 'static { fn get_or_create(&self, job: Job) -> impl std::future::Future<Output = Result<JobState, JobError>> + Send; }
2. 调整JobManager的实现
将原来的async fn改为普通函数,返回一个异步块:
impl GetOrCreate for JobManager { fn get_or_create(&self, job: Job) -> impl std::future::Future<Output = Result<JobState, JobError>> + Send { async move { let mut hs = self.jobs.lock().await; match hs.get(&job).cloned() { Some(j) => Ok(j), None => { hs.insert(job.clone(), JobState::Created); Ok(JobState::Created) } } } } }
原因说明
当直接使用具体类型Arc<JobManager>时,编译器能推断出其异步方法返回的Future是Send的;但使用泛型Trait约束时,Rust默认不会为Trait中异步方法的返回Future自动添加Send约束,因此需要显式声明,确保所有实现该Trait的类型返回的Future都满足Send要求,适配Axum多线程运行时的需求。
内容的提问来源于stack exchange,提问作者acud
相关产品推荐
相关产品推荐

