Search code examples
asynchronousrusttraitsrust-axum

Unexplained trait bound no longer satisfied when modifying Axum handler body


Recently, I've started to learn Rust to build some simple web apps with Axum. However, I ended having an unexplained error while building the app, and I could not find the reason of it.

The failing to be built application code is the following:

use axum::{
    body::{Body, boxed, StreamBody},
    Extension,
    http::Request,
    response::Response,
    Router,
    routing::get,
};
use std::{
    path::PathBuf,
    sync::{Arc, RwLock},
};

type Cache = Arc<RwLock<PathBuf>>;

async fn handle_proxy(
    cache: Extension<Cache>,
    req: Request<Body>,
) -> Response {
    let cache = cache.read().unwrap();

    if let Some(object_key) = req.uri().path().rsplitn(2, '/').next() {
        // later the cache variable will be used to build the given path
        let file = tokio::fs::File::open("/tmp/testaroo").await;
        let buf_reader = tokio::io::BufReader::new(file.unwrap());
        let stream = tokio_util::io::ReaderStream::new(buf_reader);
        return Response::builder().body(boxed(StreamBody::new(stream))).unwrap();
    }

    // The function is not fully built here, there will be more code later, useless to demo the error.

    Response::builder().body(boxed("".to_string())).unwrap()
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/:key", get(handle_proxy));

    let addr = "0.0.0.0:8080";
    axum::Server::bind(&addr.parse().unwrap()).serve(app.into_make_service()).await.unwrap();
}

When the let cache = cache.read().unwrap(); line is present (not commented above), the program is no longer building with the following error:

> cargo clippy
    Checking hello-rs-axum-failure v0.1.0 (/home/mycroft/tmp/broken-thing)
error[E0277]: the trait bound `fn(axum::Extension<std::sync::Arc<std::sync::RwLock<std::path::PathBuf>>>, axum::http::Request<axum::body::Body>) -> impl std::future::Future<Output = axum::http::Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>> {handle_proxy}: axum::handler::Handler<_, _, _>` is not satisfied
   --> src/main.rs:34:48
    |
34  |     let app = Router::new().route("/:key", get(handle_proxy));
    |                                            --- ^^^^^^^^^^^^ the trait `axum::handler::Handler<_, _, _>` is not implemented for fn item `fn(axum::Extension<std::sync::Arc<std::sync::RwLock<std::path::PathBuf>>>, axum::http::Request<axum::body::Body>) -> impl std::future::Future<Output = axum::http::Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>> {handle_proxy}`
    |                                            |
    |                                            required by a bound introduced by this call
    |
    = help: the following other types implement trait `axum::handler::Handler<T, S, B>`:
              <axum::handler::Layered<L, H, T, S, B, B2> as axum::handler::Handler<T, S, B2>>
              <axum::routing::MethodRouter<S, B> as axum::handler::Handler<(), S, B>>
note: required by a bound in `axum::routing::get`
   --> /home/mycroft/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.6.18/src/routing/method_routing.rs:403:1
    |
403 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `hello-rs-axum-failure` due to previous error

I understand that adding the line is breaking something, but why? I found out limiting the scope of the declaration (ie: putting the instruction in its own scope and copying the underlying variable) will help, but I'd really like to know why the error is happening.


Solution

  • The code in the async handler function is holding onto the guard for the cache value in the scope.

    As you discovered, this guard is not Send, so it can't be held across awaits because the execution might continue on a different thread after resuming execution from an await.

    Two possibilities to solve this:

    1. Change the code so you don't hold the guard across an await boundary. (May or may not be possible or desirable.)

    2. Use an async concurrency structure like tokio::sync::RwLock instead of the one in the standard library. These can be held across await boundaries.