Handling Result and errors in a Rust web handler
Result-based error handling in web handlers.
handler returns Result, the ? operator early-returns errors, a custom error type implements IntoResponse to map to 500, success returns 200 with data.
WHAT THIS TESTS Whether you can handle a fallible operation in a Rust web handler idiomatically, using Result and the ? operator, and connect a custom error type to the framework's response machinery.
A GOOD ANSWER COVERS Write the handler to return a Result, for example Result<Json<Data>, AppError>. Inside, call the query and apply the ? operator: let row = repo.fetch(id).await?. The ? operator checks the Result, and on Err returns early from the function, first converting the underlying error into your error type via the From trait, so a sqlx or diesel error flows into AppError automatically. Define AppError as an enum capturing the failure kinds, and implement the framework's response trait, IntoResponse in axum, to map each variant to an HTTP status and body, for instance database failures to 500 Internal Server Error and a not-found variant to 404. On the happy path you return Ok(Json(data)), which axum serializes with a 200. This keeps the handler body linear and free of nested matches, while centralizing the error-to-status mapping in one place.
COMMON WRONG ANSWERS Calling .unwrap or .expect on the Result, which panics on failure; in many setups that returns 500 only because of a panic catcher, but it is fragile and loses control. Returning a bare string instead of a typed error. Matching every error inline in each handler, duplicating mapping logic. Forgetting the From impl, so ? does not compile.
LIKELY FOLLOW-UPS How does From and the question-mark conversion chain work? How do you avoid leaking internal error details to clients while logging the full error server-side? How do you map different error variants to different status codes? How does this compare to Go's explicit if err != nil returns?
ONE CONCRETE EXAMPLE async fn get_user(...) -> Result<Json<User>, AppError> { let u = db.find(id).await?; Ok(Json(u)) }, with impl IntoResponse for AppError mapping AppError::Db to (StatusCode::INTERNAL_SERVER_ERROR, "internal error") and logging the cause. A query failure short-circuits via ? into a clean 500, while success yields 200 with the JSON user, exactly the desired behavior.
Read the original → docs.rs
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.