Rust Custom Errors
A custom error is an error type a program defines for its own failures. The usual shape is an enum, with one variant for each kind of failure.
One variant per kind of failure
The usual shape is an enum. TooLong carries the length, so the message can use it.
enum AppError {
Empty,
TooLong(usize),
}Display turns it into a message
With a Display impl on the enum, printing the error gives your sentence.
let e = AppError::TooLong(8);
println!("{e}");Error text is a fragment, not a sentence
// Display for an error writes a lowercase fragment:
write!(f, "address is not valid")
// so a report can embed it whole:
// "failed to load config: address is not valid"Callers match and react
Because the variants are public, a caller can answer each kind in its own way.
match check("toolong!") {
Err(AppError::TooLong(n)) => println!("too long: {n} chars"),
_ => println!("fine"),
}One variant holds the library error
A variant can carry a whole error from the standard library, kept inside yours as the cause.
enum AppError {
Empty,
Parse(std::num::ParseIntError),
}Give the argument back inside the error
The error carries the letter that could not be delivered. So main can fix the problem. It can try again with the very same value.
struct Undelivered {
letter: String, // the argument itself, handed back
reason: String,
}Example
use std::fmt;
#[derive(Debug)]
enum AppError { Empty, TooLong(usize) }
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { AppError::Empty => write!(f, "input is empty"), AppError::TooLong(n) => write!(f, "input too long: {} chars", n) }
}
}
impl std::error::Error for AppError {}
fn check(s: &str) -> Result<(), AppError> {
if s.is_empty() { Err(AppError::Empty) } else if s.len() > 5 { Err(AppError::TooLong(s.len())) } else { Ok(()) }
}
fn main() { println!("{}", check("").unwrap_err()); println!("{}", check("toolong!").unwrap_err()); }Key points
- Also give it Debug, and implement std::error::Error, so it works with ? and Box<dyn Error>
- The thiserror crate can write the Display and Error code for you.
- The anyhow crate gives flexible errors for whole applications.
- Override the source method of the Error trait to return that inner error.
- A report can then show the custom message.
- The cause appears under it.
- Name the error type in verb-object order, with the word Error last.
- ParseAddrError follows the standard library's own ParseIntError and RecvTimeoutError.
- Never use () as an error type: it has no message, no Error impl, and cannot travel through ? — make a small named error type instead
- An empty input is one such failure.
In this example, AppError is an enum with two variants. Empty means the input had nothing. TooLong(n) carries the input's length, so the message can say exactly how long it was. The Display code turns each variant into a sentence: "input is empty", or "input too long: 8 chars". check returns these errors, and main prints them by taking the Err value out with unwrap_err(). A caller could instead match the variant and handle Empty and TooLong in different ways.
Note
Libraries return precise, typed errors.
Applications, which mostly report errors, may use a flexible error type.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What must a type implement to be used as a standard Rust error?