Error handling can be tedious and extremely annoying... On most programming languages, but using Rust it's actually very simple !
Of course coding methods are a matter of preference and I am nobody to say this is better than something else, but here's my 2 cents on this subject, and how I prefer to do it.
How I organise error handling in my projects
What I usually do with my projects is that I setup a simple error handling typepub enum Errcode in a file src/errors.rs, and I define every possible kind of errors in it.
In the whole project, I write any function that can result in an error with a return type of Result<_, Errcode>, allowing me to use the ? operator everywhere, returning the error till it reaches the very main function of my binary.
Error conversion
When I perform a serialization using serde, or some function returning a std::io::Error, How can I transform the error to my "standardized" one ?
Here comes the From trait:
With this setup, I can use the code abusing the ? operator:
use crateErrcode;
Avoiding code repetition
If you look at the code block defining all the impl From<_> for Errcode below, you can see how this can be annoying after a while.
I repeated my code more than 2 times, it's worth spending an hour writing an automation macro ! Luckily for you, I did it for you ;-)
There are 3 parts in the process we want to automate:
- Create an enum variant with a given name
- Include a type inside this enum variant
- Implement the
From<_> for Errcodetrait on the enum
Getting the arguments of the macro
We will use the pattern [ $( $name:ident : $class:ty ),+ ], meaning that the macro define_errcodes will have the formdefine_errcodes![ NAME_A : CLASS_A, NAME_B : CLASS_B, ... ]
[ .. ]defines the characters before and after the arguments list$( ... ),+defines the repetition, saying that args are delimited by,and that there is at least one argument.( A : B )means that the two arguments in one repetition are separated by a:(it cannot be a,as it would be confusing)$name:identmeans$nameis an Identifier, same as a variable / function name$class:tymeans$classis a Type, it will be checked during the compilation
Generating code for each argument
Inside our macro "code", we will use some $( <code> )+ blocks, which will loop through all our arguments and generate the <code> for each of them.
So something like:
println!;
$Would generate something like:
println!;
println!;
println!;
println!;
// ...Our final macro code
This is now what our src/errors.rs file look:
define_errcodes!;Scoped yet global error handling
The good thing with this macro is that at any particular part of your project you can create a new error type, and will just have to add it to the list !
Then you can have things like:
// src/errors.rs
define_errcodes!;// src/server.rs
}// src/main.rs
See how in the init_server everything is supposed to return a Errcode error case but in our try_bind_port we return a ServerError ? The ? operator will perform the conversion itself, and that allows to write clean and readable code, without all the error-handling-related code.
