Rust error handling is nice but obligatory. Which makes it sometimes plenty of code.
Functions return values of type Result that is "enumeration". In Rust enumeration means complex value that has alternatives and that alternative is shown with a tag.
Result is defined as Ok or Err. The definition is generic, and both alternatives have an attribute that can be of any type.
enum Result<T, E> {
Ok(T),
Err(E),
}
Normal way to handle errors we use the match statement:
match function_to_do_nice_things() {
Ok(t) => {
println!("this went great, {}", t);
},
Err(e) => "oops"
}
If all the errors are wrapped to match statement, we get soon plenty of match statements inside each other.
One alternative to address this is to chain methods to handle the errors.
value = function_to_do_nice_things()
.and_then(other_function)
.map_err(|e| module_error_from_io_error(e))?
Example calls two functions, gets error value from the first that fails, maps it from io error to our own error and returns it from the function to the caller. If calls are ok, we unwrap the Ok result and assign it to variable value.
It is pretty difficult to find the right error mapping funtion. There are 21 of them and the descriptions are pretty cryptic.
This cheatsheet lists 21 error Result handling functions and tells what they do.
The division is
- Six functions that map results to results
- Two functions that map results to values (or results)
- Eight functions that can be used to extract Ok value from the Result or to get information on existence of Ok result
- Four functions that can be used to extract Err value or to get information of existence of Err result
- One special conversion function
The first column tells what is done to the result if it is Ok variant. The second column tells what is done to the Err variant. This hopefully makes it easier to find the right function for specific purpose.
For example, if you are looking for a function that leaves Err result as is and maps the Ok result with a function, you quickly find that map and and_then are such functions. Then you decide if you want to map simply with a function mapping the value (map) or if you want to return a full Ok/Err result with and_then function.
- r is the result what these functions address
- t is the Ok value inside r, Ok(t)
- e is the Err value inside r, Err(e)
- r2 is the second result given as an argument having t2 and e2
- f is a function that gets t as input and generates t'
- F is a function that gets t as input and generates new Result(t', e')
- g is a function that gets e as input and generates e'
Ok(t) -> ? | Err(e) -> ? | Code r: | Description |
---|---|---|---|
t -> Ok(t') | Unchanged | r.map(|t| f(t)) |
Map ok with function, error as is, mapping can not result error |
t -> (t', e') | Unchanged | r.and_then(|t| F(t)) |
Calls function for Ok value and propagates errors. When you chain these like r.and_then().and_then(), it returns result of last function or the first happened error. |
Unchanged | _e -> (t', e') | r.or_else(|_e| F()) |
In chain r.or_else(f1).or_else(f2) calls functions until one succeeds, does not call after first success, argument must return Result type. Called function gets the error value as argument but likely do not use it. |
Unused, return arg r2 (t2, e2) | Unchanged | r.and(r2) |
In chain r.and(r2).and(r3) return last ok result or first error |
Unchanged | Unused, return arg r2 (t2, e2) instead | r.or(r2) |
In chain r.or(r2).or(r3) return value of first Ok or last error, evaluates all or values |
Unchanged | e -> Err(e') | r.map_err(|e| g(e)) |
Map error with g(e), that return a normal type that is automatically converted to error result. Map function can not return an error. |
Ok -> ? | Err -> ? | Code | Description |
---|---|---|---|
t -> t' (returned as is) | e -> e' (returned as is) | r.map_or_else(|e| g(e), |t| f(t)) |
Map both Ok and Err with a function. Result can be of any type but it has to be same for both Ok and Error. Err mapping function is first because it is considered as a "default value if normal processing fails" like in the map_or. |
t -> t' (returned as is) | Literal (returned as is) | r.map_or(literal, |t| f(t)) |
Map with function. If error, use literal as a default value. Mapping function can return Result but also any other type that matches literal. Note that this does NOT meant that if mapping function fails, use literal. It means that if we can not use mapping function due to error, give the literal instead. |
Ok -> ? | Err -> ? | Code | Description |
---|---|---|---|
t | stop function and return Err(e) immediately | r? |
If error, return from the function using this same result. Function result must be compatible. |
t | panic | r.unwrap() |
Panics with error, may use e as panic message. |
t | panic with message | r.expect("string") |
unwrap() with a given panic message. |
t | Literal as t' | r.unwrap_or(literal) |
Unwrap, if error, use literal from arguments instead. |
t | e -> t' | r.unwrap_or_else(|e| g(e)) |
Extract value or derive it from error with function |
t | Default as t' | r.unwrap_or_default() |
Returns value or default for that type (if set) |
true | false | r.is_ok() |
True if ok |
Option::Some(t) | Option::None | r.ok() |
If Ok, return Option::Some(t), in case of error returns Option::None |
Ok -> ? | Err -> ? | Code | Description |
---|---|---|---|
panic | e | r.unwrap_err() |
Panics, may shows value of t |
panic | e | r.expect_err("message") |
Panics if ok, with set panic message, prints value of t |
false | true | r.is_err() |
True if error |
None | Some(e) | r.err() |
Some(e) if error or None if no error |
Ok -> ? | Err -> ? | Code | Description |
---|---|---|---|
t -> Some(t) | e -> Some(e) | r.transpose() |
Take Option (especially Option::None) out from Result |
To use r?, function must return compatible Result type. For testing, the main function and tests can return Result type (Rust 2018)
It is customary to define your own Error type for your program
pub struct MyError {};
pub type Result<T> = result::Result<T, MyError>;
impl fmt::Display for MyError {
..
}
impl fmt::Debug for MyError {
..
}
let r: Result<u32, String> = Ok(233);
let s: Result<u32, String> = Err("meaningless input");
let t: Result<(), ()> = Ok(());
Thank you for sharing this awesome gist!