Handling different responses with Axum's ImplResponse

Hi, I'm still a newb with Rust - everything is pretty confusing for me still.
I'm building a webserver with Axum in Rust, and the project (refactor branch) is on GitHub. This is the spot where I would put a link to it, but since I am a new user I am unable to link more than two links in a post.

You can browse the full breadth of my codebase here - it's very experimental and I am in no ways a good Rust programmer (yet). The most important lines for my question are linked below.

I thought it would be relatively simple to return many different types of responses composed of specific status codes, headers, and body types, but it appears things are not so. I was sure that using impl IntoResponse in my return type would work, but it didn't.

It appears that the compiler gets most heated whenever I mix IntoResponse with Response::builder(), or whenever I mix a body types like Bytes with Full<T> for any T, Bytes or String. Besides not knowing how to fix the error, I'm really confused why the API is so precise/strict with the types of the response body. It feels like I'm fighting Axum to get it to do what I want more than anything else.

The most consistent and arguably, difficult compiler error is below:

error[E0308]: mismatched types
  --> src\routes.rs:73:16
   |
47 |   pub async fn implicit_handler(Path(path): Path<String>) -> impl IntoResponse {
   |                                                              ----------------- expected because this return type...
...
54 |           return (StatusCode::BAD_REQUEST, Full::from(format!("Failed to parse epoch :: {}", parsed_epoch.unwrap_err()))).into_response();
   |                  ------------------------------------------------------------------------------------------------------------------------ ...is found to be `Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>` here
...
73 |           return Response::builder()
   |  ________________^
74 | |             .status(StatusCode::INTERNAL_SERVER_ERROR)
75 | |             .body(Full::from(
76 | |                 format!("Template Could Not Be Rendered :: {}", rendered_template.err().unwrap())
77 | |             ))
78 | |             .unwrap();
   | |_____________________^ expected `Response<UnsyncBoxBody<Bytes, ...>>`, found `Response<Full<_>>`
   |
   = note: expected struct `Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>`
              found struct `Response<axum::body::Full<_>>`
note: return type inferred to be `Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>` here
  --> src\routes.rs:54:16
   |
54 |         return (StatusCode::BAD_REQUEST, Full::from(format!("Failed to parse epoch :: {}", parsed_epoch.unwrap_err()))).into_response();
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: to return `impl Trait`, all returned values must be of the same type
   = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
   = help: if the trait `IntoResponse` were object safe, you could return a boxed trait object
   = note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
   = help: you could instead create a new `enum` with a variant for each returned type

The full compiler output with my codebase is linked <I would put it here, but again, I can only put two links in my post since I am a new user>.

The full compiler output of my current codebase is linked here.

Using return position impl Trait allows you to avoid explicitly naming what type the function returns, but it doesn't let you return multiple different types. impl Trait can only ever represent a single type, just like a type parameter can represent a single type at a time (e.g. a Vec<usize> can't also be a Vec<String>, it's either one or the other)

The simplest solution is simply to call into_response() on all of the responses before you return them. Then the return type will be the same for all of the responses.

You could also define an enum which has a separate variant for each type of response you need to return, and them implement IntoResponse for the enum.

In some cases you can work around this issue by returning a trait object since that erases the specific concrete type. But IntoResponse isn't object safe so that isn't an option[1].


  1. at least without more workarounds ↩︎

Alrighty; I'm not sure if I'd call this a solution to be honest, but if someone with much more experience recommends this as the appropriate solution, I'll take it. I switched the Response::builder() stuff to use .into_response() or converted the rest into the tuple IntoResponse as available. Resolved - for now.