Search code examples
rustrust-tokiorust-axum

How do pass and mutate shared State in handlers in Rust Axum?


I need to figure out 2 things at least so I can finish the task:

  1. How to have a shared state between handlers
  2. How to pass that state and read/modify it from handler

I've made an working example of what I think I should build but instead of my custom struct Users, I'm using integers

struct AppState {
    data: Mutex<Vec<u32>>,
}

#[tokio::main]
async fn main() {
    let mut vector = vec![1, 2, 3];
    let shared_state = Arc::new(AppState { data: Mutex::new(vector) });

    let app = Router::new()
        .route("/add", get(add_num))
        .route("/get", get(get_nums))
        .with_state(shared_state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn add_num(State(state): State<Arc<AppState>>) {
    let mut x = Arc::clone(&state);

    thread::spawn(move || {
        let mut vec = x.data.lock().unwrap();

        vec.push(318);
        let joined_string: String = vec.iter().map(|&x| x.to_string()).collect::<Vec<String>>().join(", ");
        println!("{}", joined_string);
    });
}

async fn get_nums(State(state): State<Arc<AppState>>) {
    let mut x = Arc::clone(&state);

    thread::spawn(move || {
        let mut vec = x.data.lock().unwrap();

        let joined_string: String = vec.iter().map(|&x| x.to_string()).collect::<Vec<String>>().join(", ");
        println!("{}", joined_string);
    });
}

The problem occurs when I try to do this with a custom struct instead of u32 numbers. When I define my stuct as

#[derive(Debug, Deserialize, Clone)]
pub struct User {
    first_name: String,
    age: u32
}

and try to add the route to the router with handler where I would pass the User through a JSON in a post request

...
let app = Router::new()
        .route("/user", post(add_user))
        .with_state(shared_state);
...
async fn add_user(
    Json(user): Json<User>,
    State(users_state): State<Arc<UsersState>>,
) {
    let mut users = Arc::clone(&users_state);

    thread::spawn(move || {
        let mut vec = users.users.lock().unwrap();
        vec.push(user);
    });
}

I get this error which I can't figure out

error[E0277]: the trait bound `fn(Json<User>, State<Arc<UsersState>>) -> impl std::future::Future<Output = ()> {add_user}: Handler<_, _>` is not satisfied
   --> src/main.rs:29:30
    |
29  |         .route("/user", post(add_user))
    |                         ---- ^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Json<User>, State<Arc<UsersState>>) -> impl std::future::Future<Output = ()> {add_user}`
    |                         |
    |                         required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S>`:
              <Layered<L, H, T, S> as Handler<T, S>>
              <MethodRouter<S> as Handler<(), S>>
note: required by a bound in `post`
   --> /home/dmjne/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.4/src/routing/method_routing.rs:389:1
    |
389 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^----^^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `post`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.

What am I doing wrong here? What am I missing?

The solution that I am trying is based upon Sharing state with handlers from the axum documentation


Solution

  • The error is that the Json parameter is first and the State parameter is second. In Axum there can only be one extractor which consumes the request body (FromRequest in axum terminology) and it must be the last one.

    As the compiler is not super good on this sort of errors, Axum provides a debug_handler attribute which you can set on handlers to try and understand why they're not working.

    Also why are you spawning threads from within your handlers? And why are you cloning your Arc despite getting an owned arc and only using it once?