Skip to content

Instantly share code, notes, and snippets.

@e-t-u
Last active April 21, 2024 09:18
Show Gist options
  • Star 79 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save e-t-u/70f25d4566468adc43a4df43667cedb6 to your computer and use it in GitHub Desktop.
Save e-t-u/70f25d4566468adc43a4df43667cedb6 to your computer and use it in GitHub Desktop.

Rust Error Handling Cheatsheet - Result handling functions

Introduction to Rust error handling

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"
}

Method chaining in error handling

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.

Cheatsheet

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.

In these examples

  • 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'

Mapping result to result

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.

Mapping Result to any type

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.

Extract Ok value

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

Extract error

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

Convert

Ok -> ? Err -> ? Code Description
t -> Some(t) e -> Some(e) r.transpose() Take Option (especially Option::None) out from Result

Question mark operator

To use r?, function must return compatible Result type. For testing, the main function and tests can return Result type (Rust 2018)

Own errors

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 {
  ..
}

Generating results for testing etc.

let r: Result<u32, String> = Ok(233);
let s: Result<u32, String> = Err("meaningless input");
let t: Result<(), ()> = Ok(());
@LyleScott
Copy link

I've referred to this doc several times this week, so I really appreciate a concise reference like this! 🍻

@abitofhelp
Copy link

Thank you for sharing this awesome gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment