Stuart Hinson bio photo

Stuart Hinson

Ruby, Rust & Clojure programmer.


LinkedIn Github

A common question in #rocket is how to go beyond the basics in error handling. Specifically, there’s a bit of a jump that’s needed to connect the dots between defining a custom error type (e.g. with thiserror) and the Responder trait.

We can put these together and write handlers with idiomatic early returns, e.g.

#[derive(Error, Debug)]
pub enum Error {
    #[error("HTTP Error {source:?}")]
    Reqwest {
        #[from] source: reqwest::Error,
    #[error("SerdeJson Error {source:?}")]
    SerdeJson {
        #[from] source: serde_json::Error,

async fn hello() -> Result<Json<Todo>, Error> {
    // a
    let response = reqwest::get("")

    let todo: Todo = serde_json::from_str(&response)?;

Setting aside the contrived logic, this is error handling as we’d expect in Rust – we’re leaning on ? and its implicit into to map errors into our Error type.

But, in a real-world application, we’re not typically happy to simply render a 500 back to the user. How do we respond differently to different error conditions? How do we log an error into a monitoring service like Sentry?

Here (and as of 0.5), the best practice I’ve found is to implement Responder for your Error type

impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
        // log `self` to your favored error tracker, e.g.
        // sentry::capture_error(&self);

        match self {
            // in our simplistic example, we're happy to respond with the default 500 responder in all cases 
            _ => Status::InternalServerError.respond_to(req)

With this approach, we have a natural place to centralize error tracking as well as flexibility in the responses we send from the server. Not bad!