I saw the call for blog posts in the 2021 rust roadmap, so I figured I’d throw in my 2 cents, since I’ve been working on a project recently that uses rust on the backend. As the title suggests, I’ve been finding myself spending too much time on error handling.

I want to preface this by saying I’m just using rust for hobby projects. If I were writing rust in production or for a larger project I probably wouldn’t care that error handling takes some work to get right, I’d be more willing to put in the time to learn more about the best practices. As it stands though, I just want to code the happy path and getting stuck on error-handling to get the compiler to accept what I’m writing can rob me of motivation.

As a rust amateur, these difficulties could just be a lack of knowledge. Potentially I’ll withdraw all this when someone messages me saying “you know, you can just use <X>”, Cunninham’s law style.

Why not panic

If I was writing a script, I could just slap unwrap on all the Result/Option values, and leave it at that. I’ve been writing a server though, so there are errors that occur where I don’t want to panic. If something goes wrong unexpectedly I don’t want the whole server to go down, I’d rather be failing requests.

Ideal world

Error handling is way better now than when I last used rust a few years ago. Having the ? syntax available for Result and Option now is awesome. In my ideal rust development setup, I’d be able to just append every function returning an Option/Result with a ?, and go on thinking about the happy path. Since this is what I’m viewing as an ideal setup, I’ll just be focusing on the problems preventing this from being reality.

Options and ?

I’m using sled, which has a get method that returns a Result<Option<T>>. For most cases, I don’t really care whether there was an error with the db or the record wasn’t found (again, hobby project, not a production site). What I’d want to do is this:

let house = db.houses.get(uuid)??;

Unfortunately, rust complains about this usage:

error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied
   --> src/main.rs:119:37
    |
119 |     let house = db.houses.get(uuid)??;
    |                                     ^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
    |
    = note: required because of the requirements on the impl of `std::convert::From<std::option::NoneError>` for `anyhow::Error`
    = note: required by `std::convert::From::from`

There’s an existing issue that has seen some discussion around this. I can’t speak for the trade-offs, but it would be nice if I could just slap a ? on here.

Errors in closures

Because of the above issue, I ended up doing stuff like this for fetching from the db, using the anyhow crate:

let house_uuids = db
    .user_investments
    .get(user.email)?
    .ok_or_else(|| anyhow!("No user investments"))?;

This converts the Option<T> to a Result<T>, so I can put a ? on there. However, recently I needed to fetch from the db in a loop, as part of a filter, like this:

new_house_uuids = new_house_uuids
    .into_iter()
    .filter(|new_house_uuid| {
        let existing_owner = db.investment_users.get(new_house_uuid)?;
        existing_owner.is_some()
    })
    .collect::<Vec<String>>();

The behavior I’d like for the ? is the same as when it’s used outside of the closure: exit the function with the error value. But it’s obvious why this doesn’t work, the closure passed to filter doesn’t return a Result:

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
   --> src/main.rs:148:34
    |
147 |           .filter(|new_house_uuid| {
    |  _________________-
148 | |             let existing_owner = db.investment_users.get(new_house_uuid)?;
    | |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a closure that returns `bool`
149 | |             match existing_owner {
150 | |                 Some(_) => false,
151 | |                 None => true,
152 | |             }
153 | |         })
    | |_________- this function should return `Result` or `Option` to accept `?`
    |
    = help: the trait `std::ops::Try` is not implemented for `bool`
    = note: required by `std::ops::Try::from_error`

I’m not sure what a solution would look like here. I know using a ? to affect the outermost function would be more unexpected than the current setup. It’s just unfortunate that I have to resort to “old-school” error-handling when I’m in a filter:

new_house_uuids = new_house_uuids
    .into_iter()
    .filter(|new_house_uuid| {
        let existing_owner = db.investment_users.get(new_house_uuid);
        match existing_owner {
            Ok(Some(_)) | Err(_) => false,
            Ok(None) => true,
        }
    })
    .collect::<Vec<String>>();

Everything else is awesome

The point of calling for these rust blog posts is obviously to gather constructive criticism, but it does feel wrong to only complain when my overall experience of using rust has been overwhelmingly positive. So I just wanted to say that rust has been a pleasure to use in general, and the developer experience is vastly better than when I used it the first time four or five years ago.