Using axum, change in function body, not signature, causes "trait not satisfied" error?

I have some code that uses the axum web app crate. Snippets below. The weird thing is that it compiled, without the await on send_dev_notification. That’s a bug, so I added the await, and then it fails to compile with the error below.

I’m confused by two things.

  1. It’s telling me to use #[axum::debug_handler] when I already have.
  2. I changed the internals of a function inside another function called by send_daily_email, yet that somehow changed send_daily_email enough to trigger this error?

What is going on?

let admin_app = Router::new()        
        .route("/send-daily-email", post(send_daily_email))
        .with_state(pool.clone());
...

#[axum::debug_handler]
async fn send_daily_email(State(pool): State<PgPool>) -> String {
    let config = CONFIG.get().unwrap();
    let table = config.get("web-log-table").unwrap().as_str().unwrap();
    let res = job_watch::email_daily_summary(&pool, table).await;
    match res {
        Ok(_) => "OK".into(),
        Err(e) => "Error".into()
    }
}
pub async fn email_daily_summary(pool: &PgPool, log_table: &str) -> Result<(), Box<dyn std::error::Error>> {
    ...
    // was: email::send_dev_notification("Daily Server Summary", &body);
    email::send_dev_notification("Daily Server Summary", &body).await;
    Ok(())
}

Error:

error[E0277]: the trait bound `fn(State<Pool<Postgres>>) -> ... {send_daily_email}: Handler<_, _>` is not satisfied
   --> my-web-site/src/lib.rs:177:42
    |
177 |         .route("/send-daily-email", post(send_daily_email))
    |                                     ---- ^^^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(State<Pool<Postgres>>) -> ... {send_daily_email}`
    |                                     |
    |                                     required by a bound introduced by this call
    |
    = note: Consider using `#[axum::debug_handler]` to improve the error message
    = help: the following other types implement trait `Handler<T, S>`:
              `MethodRouter<S>` implements `Handler<(), S>`
              `axum::handler::Layered<L, H, T, S>` implements `Handler<T, S>`
note: required by a bound in `post`
   --> /Users/rob/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/axum-0.8.6/src/routing/method_routing.rs:445:1
    |
445 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^----^^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `post`

The return types of async fn and any -> impl Trait "leak" their auto-traits, which means that a change to the function body can result in errors like this even when the signature didn't change. That's what I suspect is going on.

You may be able to test that theory with something like:

pub fn email_daily_summary(pool: &PgPool, log_table: &str)
    -> impl Send + Future<Output = Result<(), Box<dyn std::error::Error>>>
{
    async move {
        // (Ensure we always capture everything)
        let (pool, log_table) = (pool, log_table);
        // ...
        email::send_dev_notification("Daily Server Summary", &body).await;
        Ok(())
    }
}

And see if you get an error about the future not implementing Send.

Yes, I get an error: “future created by async block is not Send”.

I don’t know how to fix that. I’m calling the mail-send crate, so maybe some of it’s async functions are returning non-Send futures. (?)

(This are mostly shots in the dark; someone with more async experience may come along with better advice.)

With the -> Send + Future<..> version, you can try moving as many things outside of the async move as possible to see if you can avoid capturing something that's not Send (or avoid the compiler thinking it's alive across the await).

pub fn email_daily_summary(pool: &PgPool, log_table: &str)
    -> impl Send + Future<Output = Result<(), Box<dyn std::error::Error>>>
{
    // ...create `body`...
    // ...maybe even drop everything you can...
    async move {
        // mention as few things as possible for minimal capturing
        email::send_dev_notification("Daily Server Summary", &body).await;
        Ok(())
    }
}

Though if you have more .awaits this may not be change much.

You could browse this meta-issue and it's sub-issues for other potential workarounds.

You could try to track down what exactly is not Send and attempt to replace it with something Send.