4 minutes
As a hobbyist rust developer, I want to think less about error handling
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.
Option
s 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.
If you play games, my PSN is mbuffett, always looking for fun people to play with.
If you're into chess, I've made a repertoire builder. It uses statistics from hundreds of millions of games at your level to find the gaps in your repertoire, and uses spaced repetition to quiz you on them.
If you want to support me, you can buy me a coffee.
818 Words
2020-09-08 22:50 +0000