Axum + Generics + Traits: Trait bound not satisfied

Objective: The AppState carries a transmitter (TX) that is supposed to forward any HTTP Post (to /beats) to a new task spawned in async main(). TX, can send any generic P implementing the HandlePulse trait.

Error: Following is compiler diagnostics:

rustc: the trait bound `fn(State<Arc<AppState<_>>>, Json<_>) -> impl Future<Output = Result<Json<_>, std::io::Error>> {root::<_>}: Handler<_, _>` is not satisfied
Consider using `#[axum::debug_handler]` to improve the error message
the following other types implement trait `Handler<T, S>`:
  `Layered<L, H, T, S>` implements `Handler<T, S>`
  `MethodRouter<S>` implements `Handler<(), S>`

debug_handler cannot be used here as it doesn't support generics (yet).

Kindly pour in the insights.

Erroneous code:

use axum::{extract::State, routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use std::{io, sync::Arc};
use tokio::sync::broadcast;

#[derive(Deserialize, Serialize, Clone, Debug)]
struct Pulse {
    payload: String,
}

struct AppState<P> {
    TX: Arc<broadcast::Sender<P>>,
}

trait HandlePulse {
    fn process_event(&self) -> Result<(), io::Error>;
}

impl<P> AppState<P>
where
    P: HandlePulse + Clone,
{
    fn new() -> Self {
        let (TX, _) = broadcast::channel(112);

        let shared_state = Self {
            TX: Arc::new(TX.clone()),
        };

        shared_state
    }
}

impl HandlePulse for Pulse {
    fn process_event(&self) -> Result<(), io::Error> {
        dbg!(self.clone().payload);

        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let app_state: Arc<AppState<Pulse>> = Arc::new(AppState::new());

    // tokio::spawn a task and use trait implementation to handle message.

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    let app = Router::new()
        .route("/beats", post(root))  // ERROR HAPPENS HERE.
        .with_state(app_state);
    axum::serve(listener, app).await.unwrap();
}

async fn root<P>(
    State(state): State<Arc<AppState<P>>>,
    Json(payload): Json<P>,
) -> Result<Json<P>, io::Error>
where
    P: HandlePulse + Clone + Send + Sync + 'static,
{
    if let Err(e) = state.TX.send(payload.clone()) {
        eprintln!("Error while sending: {:?}", e.to_string());
    }

    // transmitting logic.

    Ok(Json(payload))
}

Thank you for your support.

1 Like

Perhaps the requirement for AppState<P>: Serialize here?

(I'm just doc chasing.)

But Pulse (struct) already derives the Serialize trait from serde.
I assume that will do the serialization trick in this occasion.

I think I got some inputs and outputs crossed, sorry.

std::io::Error needs to implement IntoResponse in axum::response - Rust

You may want to use something like axum::http::Response<String> for the error type. It will give you a lot of flexibility for error reporting to the client. But other types are possible, like (axum::http::StatusCode, String).

2 Likes

Thank you @parasyte for the right pointers.
+1 for http::Response suggestion.

I think I need to go through Higher-Rank Trait Bounds.


Going through serde's deserializer lifetimes also helped me fix the issues related to unsatisfied trait bounds.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.