4 ms·
This is amazing! But there seems to be something missing: error handling. After using Rust on and off for a couple of years, a thing I still don’t understand i
by da39a3ee 6y ago
This is amazing!
But there seems to be something missing: error handling. After using Rust on and off for a couple of years, a thing I still don’t understand is how to write idiomatic code which deals with multiple different error types.
- mesaframe 6y agoEnums?
- papaf 6y agoI sill don’t understand is how to write idiomatic code which deals with multiple different error types. I am not sure if it is idiomatic but I have been following the guidelines int this article for general error handling: https://nick.groenen.me/posts/rust-error-handling/ https://nick.groenen.me/posts/rust-error-handling/ And this article for having context: https://kazlauskas.me/entries/errors.html https://kazlauskas.me/entries/errors.html
- da39a3ee 6y agoThank you. (I'd come across the first but not the second). It looks like there is nothing blocking me apart from the need to find a clear morning to which to devote myself to the task of doing error handling properly in my project.
- davidhyde 6y agoIf you are writing your own library then your best bet is to define your own Error enum in each module. Below each Error enum write a bunch of From trait implementations to convert errors into a custom variant in your Error enum. This allows you to use the "?" operator to "bubble" errors up through your library and build some sort of useful printable error tree. Don't used boxed errors and don't use std:io:Error for your own custom purposes. Doing things this way will mean that consumers of your library will not be bound to use some error handling library that they may not want to use. It is also very fast. If this is not a library then feel free to use any number of error handling helper libraries out there. It would be unfair to single one of them out. However, once I got the hang of writing my on custom errors I ended up using them for my binary applications too.
- da39a3ee 6y agoThanks, that's very helpful and may well be the encouragement I apparently needed! It is a standalone CLI application but it might want to offer a library in the future. And in any case I am attracted to following your suggestion regarding custom enums, partly because it seems more likely to dispel my lack of understanding of the topic.
- davidhyde 6y agoCheers! It's a daunting topic with many different ways of doing things and it is difficult to find reading material on the topic of idiomatic error handling. I just read through a lot of popular code bases and picked out what I liked. I found that using custom errors was the simplest approach for me albeit a little verbose.
- shepmaster 6y ago> If you are writing your own library then your best bet is to define your own Error enum in each module This is exactly the path that my SNAFU library takes. It’s a procedural macro attached to an enum that generates the From, Display and Error traits for you. This way, consumers of your code don’t need to know anything beyond the fact that you implement Error and any inherent public methods or traits you choose to expose. The implementation provided by SNAFU is internal to your code. https://docs.rs/snafu/0.6.10/snafu/ https://docs.rs/snafu/0.6.10/snafu/
- iudqnolq 6y agoMy sibling posts a great general overview, and they make a good point about not biasing specific libraries. When I was starting out in Rust, however, I found value in just doing what everyone else was doing and then deciding if I wanted to do things my own way once I was confident I could do the popular way. In that vein, here's doing error handling like a lemming. Library: thiserror Application: anyhow (or eyre if you want the same thing but with fancy colors and nicer backtraces). Make sure you #[from] to derive From a lot. The value is in chains. Here's an example I just wrote #[derive(thiserror::Error)] enum LoadError { #[error("Failed to parse")] Parse(#[from] ParseError), #[error("Failed to acquire lock")] Lock(#[from] file_lock::AcquireError), #[error("IO error") Io(#[from] std::io::Error), } ParseError and AcquireError are defined in a similar style. Then I have a function fn load(...) -> Result<..., LoadError> { let lock = FileLock::acquire(...)?; let parsed = parse(...)?; Ok(parsed) } The magic is that ? calls .into on the error, which uses a From implemention to convert the error into the error type the function returns. This means the ParseError gets converted into a LoadError::Parse, for example. Now we've got that nice semantic cause chain, we use anyhow to display it. fn main() -> anyhow:: Result<()> { let index = load(...)?; // ... Ok(()) } If index fails I'll get something like this printed: Error: Failed to acquire lock Caused by: Failed to open lock No such file or directory (OS error 2) It feels like a superpower to have a high-level function fail and see the massive list of errors all the way down to the root cause.
- da39a3ee 6y agoThank you!