Error Handling
Termination
// panic! stops the program right away
panic!("string literal");
// std::process::exit
fn run_app() -> Result<(), ()> {
// Application logic here
Ok(())
}
fn main() {
std::process::exit(match run_app() {
Ok(_) => 0,
Err(err) => {
eprintln!("error: {:?}", err);
1
}
});
}
// exit code is 0 on Linux; 256 on Windows
use std::process;
process::exit(0x0100);
// std::process::abort
// similar when Cargo.toml set `panic="abort"`
// `panic!` still call panic hook, `abort` will not
std::process::abort();
Option »
// Option is a type of enum in standard lib
// <T> generic type parameter
enum Option<T> {
None,
Some(T),
}
// need to specity type for None
let abs: Option<i32> = None;
let some = Some(5);
let some_string = Some("string");
// matching with Option<T>
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
// Option impl `map` methods and `map_or`
pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
let maybe_some_string = Some(String::from("Hello, World!"));
// `Option::map` takes self *by value*, consuming `maybe_some_string`
let maybe_some_len = maybe_some_string.map(|s| s.len());
assert_eq!(maybe_some_len, Some(13));
// query variant
option.is_some() // return bool
option.is_none()
// transform contained value to Result
option.ok_or(err) // Some(v) to Ok(v), and None to Err(err)
option.ok_or_else(Fn) // None to value of Err using FnOnce
// methods on Some variant
.filter() // return Some(t) or None
.flatten() // converts from Option<Option<T>> to Option<T>
.map() // convert Option<T> to Option<U> with fn, None unchanged
.map_or(default: U, f: FnOnce(T) -> U) // provide default value
.map_or_else(D, F) // use Fn D if None, or use Fn F
// create Some
.zip(Option<U>) -> Option<(T,U)> // None if U is None
// use collect to turn a slice of Options to Option of a slice
// return None if any of the item is None
Result »
enum Result<T,E> {
Ok(T),
Err(E),
}
// example
match result {
Ok(v) => println!("working with version: {:?}", v),
Err(e) => println!("error parsing header: {:?}", e),
}
// Result also has `map`, `map_or` methods
pub fn map<U, F>(self, op: F) -> Result<U, E>
where
F: FnOnce(T) -> U,
fn name() -> Cow<'static, str> {
std::env::var("USER")
.map(|v| v.into())
.unwrap_or(Cow::Borrowed("whoever you are"))
}
// is_ok() and is_err(), return bool
// this method does not consume `self`
if some_result.is_ok() { /* do something */ }
// return ref and not to consume result
result.as_ref() -> Result<&T, &E>
result.as_mut() -> Result<&mut T, &mut E>
// consumes `self`
.ok() //convert from Result<T,E> to Option<T>
.err() //convert to Option<E>, either None or Some(E)
let x: Result = Ok(2);
x.ok(); // Some(2)
.map(Fn) -> Result<U, E> // from Result<T, E>
.map_err(Fn) -> Result<T, F> // Ok unchanged
.map_or(U, Fn) -> U // default U, or use Fn
.map_or_else(Fn1, Fn2) -> U // Fn1 for E, Fn2 for Ok
// use collect to turn slice of a Result to Result<slice, E>
// a string of numbers that may contain letters
"12345028a0329b".chars().map(|c| c.to_digit(10).ok_or(SomeError))
.collect::<Result<Vec<_>, _>>()?
// Result Type ALiases
type Result<T> = result::Result<T, Error>;
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T, GenericError>;
Error Handling
unwrap
// unwrap(): can be used for Option and Result
// return T if Ok, and pacnic! if Err
use std::fs::File;
File::open("hello.txt").unwrap();
// upwrap_or(): return Ok value or a default
.unwrap_or(T) -> T
.unwrap_or_else(FnOnce(E)->T) -> T // return Ok value or computers from Fn
.unwrap_or_default() -> T // T must impl Default
// unwrap_err(): panics if the value is Ok, and returns the value of Err
error.unwrap_err();
// expect(): add error message
File::open("hello.text").expect("Failed to open hello.txt");
? operator
- Ending the expression with ? will result in the unwrapped success (Ok) value
- unless the result is Err, in which case Err is returned early from the enclosing function.
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = first_number_str.parse::<i32>()?;
let second_number = second_number_str.parse::<i32>()?;
Ok(first_number * second_number)
}
// another example
use std::fs::File
use std::io;
fn open_file() -> Result<File, io::Error> {
let f = File::open("hello.txt")?;
Ok(f)
}
// for main(), need to return Result<T, E>
// use Box<dyn error::Error> if there is multiple E
use std::error;
use std::fs::File;
fn main -> Result<(), Box<dyn error::Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
Custom Error Type
From Trait
From trait allows ? operator to convert to custom error type
- implement
from method for each error type
use std::{fs, io, num};
enum CustomError {
IoError(io::Error),
ParseError(num::ParseIntError),
}
impl From<io::Error> for CustomError {
fn from(e: io::Error) -> Self {
CustomError::IoError(e)
}
}
impl From<num::ParseIntError> for CustomError {
fn from(e: num::ParseIntError> -> Self {
CustomError::ParseError(e)
}
}
fn open_file(f: &str) -> Result<i32, CustomError> {
let mut c = fs::read_to_string(&f)?;
let n: i32 = c.trim().parse()?;
Ok(n)
}
Error Trait
// usually not necessary to implement any method
// TODO: when `Error` trait is needed
impl std::io::Error for CustomErrorType {}
// impl source method to reveal lower-level error
impl Error for CustomError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let CustomError::ParseInt(e) = &self {
return Some(e);
}
None
}
}
// unwrap since it's an Option
let err = error.source().unwrap();
// another way is impl Display
impl Display for CustomError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ParseInt(e) => write!(f, "error parsing INT {}", e),
_ => write!(f, "other types of error"),
}
}
}
// error downcast
error.downcast_ref::<ErrorType>()
Print Errors
- All standard library
Error types implement std::error::Error
println!() // both {} and {:?}
err.to_string()
err.source() // impl `source`
// example to print all available information
use std::error::Error;
use std::io::{Write, stderr};
/// Dump an error message to `stderr`.
///
/// If another error happens while building the error message or
/// writing to `stderr`, it is ignored.
fn print_error(mut err: &dyn Error) {
// use `writeln!` instead of `eprintln!` because `eprintln!` panics if error occurs
let _ = writeln!(stderr(), "error: {}", err);
while let Some(source) = err.source() {
let _ = writeln!(stderr(), "caused by: {}", source);
err = source;
}
}
fn main() {
if let Err(err) = run() {
print_error(&err);
std::process::exit(1);
}
}