🎓 A1Learner
🔍

Rust Boxed Errors

A boxed error is any error stored in a Box<dyn Error>. One function can fail with an io error or a parse error.

Name the box in the signature

Box<dyn Error> in the error slot accepts any error type. The choice happens at run time.

fn load() -> Result<i32, Box<dyn Error>> {

? boxes the error for you

Each ? converts its error into the box as it returns, so unlike errors share one function.

let text = fs::read_to_string(path)?;
let n: i32 = text.trim().parse()?;

Shorten it with an alias

Most programs rename the long signature once, then reuse the short name.

type BoxResult<T> = Result<T, Box<dyn Error>>;

Fish the real type back out of the box

failing() returns Box<dyn Error>. The box hides the type, but downcast_ref asks it a direct question.

let err = failing().unwrap_err();
println!("message: {}", err);
if let Some(parse_err) = err.downcast_ref::<std::num::ParseIntError>() {
    println!("it was a parse error: {:?}", parse_err.kind());
}

Send + Sync travels between threads

Send means a value may safely move to another thread. Sync means threads may safely share it. Add the two markers when the boxed error must move to another thread.

type SharedResult<T> = Result<T, Box<dyn Error + Send + Sync>>;

Example

use std::error::Error;
use std::fs;
type BoxResult<T> = Result<T, Box<dyn Error>>;
fn load_total(path: &str, bonus_text: &str) -> BoxResult<i32> {
    let text = fs::read_to_string(path)?;
    let base: i32 = text.trim().parse()?;
    let bonus: i32 = bonus_text.parse()?;
    Ok(base + bonus)
}
fn main() {
    fs::write("score.txt", "40").unwrap();
    println!("total = {}", load_total("score.txt", "2").unwrap());
    match load_total("score.txt", "abc") {
        Ok(n) => println!("total = {}", n),
        Err(e) => println!("error = {}", e),
    }
}

Key points

  • The box holds any error that implements the Error trait.
  • The concrete type is chosen at run time.
  • The concrete type is chosen when the failure happens.
  • The drawback: callers no longer know the exact error type at compile time.
  • Matching on the kind of failure is no longer easy.

In this example, load_total can fail in two different ways. read_to_string fails with an io error. parse fails with a parse error. Both return through the same BoxResult, because each ? converts its error into the box on the way out. main first writes a small file holding 40, then loads it and adds the bonus 2: total = 42. The second call passes the text abc as the bonus. Parsing fails. The boxed error travels to main. Printing it shows the parse error's own message: invalid digit found in string.

Note

Boxing suits applications that mostly report errors.

Libraries that promise callers a choice of reactions usually return a precise error type instead.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What is the main drawback of returning Box<dyn Error>?