Search code examples
rust

Future is not Send when passing type with a trait bound


I have a type that implements a certain functionality I'd like to put behind a trait (because I'd like to use automock in order to test the usages of that module in related dependencies). When I try to pass the object wrapped in an Arc to an axum handler without generics (to enforce the trait bound) - everything compiles well. However, when I introduce the trait bound into the story (on the callers of the trait), I get the ominous future returned by ... is not Send. Peppering Send + Sync + 'static all over the place did not do anything to improve the situation and I really can't wrap my head around why this is happening.

The full code is in the gist below, however, for posterity I will try to summarize it here:

use mockall::automock;
use tokio::sync::Mutex;

pub struct JobManager {
    jobs: Mutex<HashMap<Job, JobState>>,
}

#[derive(Clone, Error, Debug, PartialEq)]
pub enum JobError {
  ...
}

#[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 => {
              ... Ok or Err ...
            }
        }
    }
}

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(args.bind_address)
        .await
        .unwrap();
 ...
}


fn router<T: GetOrCreate + Send + Sync + 'static>(job_manager: Arc<T>) -> Router {
    Router::new()
        .route(
            "/job/.....",
            get(get_job),
        )
        .with_state(job_manager)
        .....
     }

#[serde_as]
#[derive(Deserialize)]
struct HexStr<const COUNT: usize>(#[serde_as(as = "serde_with::hex::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((no, ch, diff, m)): Path<(
        u8,
        HexStr<8>,
        HexStr<32>,
        HexStr<32>,
    )>,
) -> Result<job_manager::JobState, job_manager::JobError> {
    manager
        .get_or_create(job_manager::Job {
            no,
            ch: ch.as_array(),
            diff: diff.as_array(),
            m: m.as_array(),
        })
        .await
}

The compile error is as follows:

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<job_manager::JobState, job_manager::JobError>>`, the trait `Send` is not implemented for `impl Future<Output = Result<job_manager::JobState, job_manager::JobError>>`, which is required by `fn(State<Arc<_>>, axum::extract::Path<(u8, HexStr<8>, HexStr<32>, HexStr<32>)>) -> impl Future<Output = Result<job_manager::JobState, job_manager::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_manager::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<job_manager::JobState, job_manager::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;
    |

Once router signature change in the following manner, the code compiles:

- fn router<T: GetOrCreate + Send + Sync + 'static>(job_manager: Arc<T>) -> Router {
+ fn router(job_manager: Arc<JobManager>) -> Router {

Full example (does not compile): https://gist.github.com/acud/f32d653dedd63b52069cc50d2de8801f

Does compile: https://gist.github.com/acud/51019803a46832fb2a916470169ad0d9


Solution

  • The problem is this trait:

    pub trait GetOrCreate: Send + Sync + 'static {
        async fn get_or_create(&self, job: Job) -> Result<JobState, JobError>;
    }
    

    Because this is a trait, and the underlying future implementations can differ, the compiler cannot "see through" the function to determine any auto traits that the future might implement. It has to take the trait itself as the final authority about what the function returns, and the Send bound is not specified here.

    To indicate that implementations must return a future implementation that is Send, desugar the async fn syntax into a fn that returns impl Future, and then add the Send bound:

    pub trait GetOrCreate: Send + Sync + 'static {
        fn get_or_create(
            &self,
            job: Job,
        ) -> impl Future<Output = Result<JobState, JobError>> + Send;
    }
    

    Because async fn is sugar, you can continue to use async fn in the trait implementations.


    Once RTN lands, you'll be able to leave GetOrCreate as it's defined, and move the Send requirement to a bound or router and get_job, if you so desire. This will allow your trait to be implemented regardless of whether the returned future is Send, instead applying that constraint only where it's actually needed.


    (Note that you do not need many of the Send + Sync + 'static bounds that you added trying to resolve the issue. I would remove as many as the compiler will allow you to.)