From 827140f0d788a0ac3b73faec931e18c6b915e059 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 7 Feb 2023 22:28:00 +0100 Subject: [PATCH] Document gotchas with `impl IntoResponse` (#1736) --- axum/src/docs/response.md | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/axum/src/docs/response.md b/axum/src/docs/response.md index aebaa9df..2afe4760 100644 --- a/axum/src/docs/response.md +++ b/axum/src/docs/response.md @@ -4,6 +4,7 @@ Types and traits for generating responses. - [Building responses](#building-responses) - [Returning different response types](#returning-different-response-types) +- [Regarding `impl IntoResponse`](#regarding-impl-intoresponse) # Building responses @@ -218,6 +219,108 @@ fn something_else() -> bool { } ``` +# Regarding `impl IntoResponse` + +You can use `impl IntoResponse` as the return type from handlers to avoid +typing large types. For example + +```rust +use axum::http::StatusCode; + +async fn handler() -> (StatusCode, [(&'static str, &'static str); 1], &'static str) { + (StatusCode::OK, [("x-foo", "bar")], "Hello, World!") +} +``` + +Becomes easier using `impl IntoResponse`: + +```rust +use axum::{http::StatusCode, response::IntoResponse}; + +async fn impl_into_response() -> impl IntoResponse { + (StatusCode::OK, [("x-foo", "bar")], "Hello, World!") +} +``` + +However `impl IntoResponse` has a few limitations. Firstly it can only be used +to return a single type: + +```rust,compile_fail +use axum::{http::StatusCode, response::IntoResponse}; + +async fn handler() -> impl IntoResponse { + if check_something() { + StatusCode::NOT_FOUND + } else { + "Hello, World!" + } +} + +fn check_something() -> bool { + # false + // ... +} +``` + +This function returns either a `StatusCode` or a `&'static str` which `impl +Trait` doesn't allow. + +Secondly `impl IntoResponse` can lead to type inference issues when used with +`Result` and `?`: + +```rust,compile_fail +use axum::{http::StatusCode, response::IntoResponse}; + +async fn handler() -> impl IntoResponse { + create_thing()?; + Ok(StatusCode::CREATED) +} + +fn create_thing() -> Result<(), StatusCode> { + # Ok(()) + // ... +} +``` + +This is because `?` supports using the [`From`] trait to convert to a different +error type but it doesn't know which type to convert to, because we only +specified `impl IntoResponse` as the return type. + +`Result` doesn't always work either: + +```rust,compile_fail +use axum::{http::StatusCode, response::IntoResponse}; + +async fn handler() -> Result { + create_thing()?; + Ok(StatusCode::CREATED) +} + +fn create_thing() -> Result<(), StatusCode> { + # Ok(()) + // ... +} +``` + +The solution is to use a concrete error type, such as `Result`: + +```rust +use axum::{http::StatusCode, response::IntoResponse}; + +async fn handler() -> Result { + create_thing()?; + Ok(StatusCode::CREATED) +} + +fn create_thing() -> Result<(), StatusCode> { + # Ok(()) + // ... +} +``` + +Because of this it is generally not recommended to use `impl IntoResponse` +unless you're familiar with the details of how `impl Trait` works. + [`IntoResponse`]: crate::response::IntoResponse [`IntoResponseParts`]: crate::response::IntoResponseParts [`StatusCode`]: http::StatusCode