Error Handling
Handle errors correctly in main
Handles error that occur when trying to open a file that does not exist. It is achieved by using error-chain, a library that takes care of a lot of boilerplate code needed in order to handle errors in Rust.
Io(std::io::Error)
inside foreign_links
allows automatic
conversion from std::io::Error
into error_chain!
defined type
implementing the Error
trait.
The below recipe will tell how long the system has been running by
opening the Unix file /proc/uptime
and parse the content to get the
first number. Returns uptime unless there is an error.
Other recipes in this book will hide the error-chain boilerplate, and can be seen by expanding the code with the ⤢ button.
use error_chain::error_chain; use std::fs::File; use std::io::Read; error_chain!{ foreign_links { Io(std::io::Error); ParseInt(::std::num::ParseIntError); } } fn read_uptime() -> Result<u64> { let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or("Cannot parse uptime data")? .parse()?) } fn main() { match read_uptime() { Ok(uptime) => println!("uptime: {} seconds", uptime), Err(err) => eprintln!("error: {}", err), }; }
Avoid discarding errors during error conversions
The error-chain crate makes matching on different error types returned by
a function possible and relatively compact. ErrorKind
determines the error
type.
Uses reqwest::blocking to query a random integer generator web service. Converts
the string response into an integer. The Rust standard library,
reqwest, and the web service can all generate errors. Well defined Rust errors
use foreign_links
. An additional ErrorKind
variant for the web service
error uses errors
block of the error_chain!
macro.
use error_chain::error_chain; error_chain! { foreign_links { Io(std::io::Error); Reqwest(reqwest::Error); ParseIntError(std::num::ParseIntError); } errors { RandomResponseError(t: String) } } fn parse_response(response: reqwest::blocking::Response) -> Result<u32> { let mut body = response.text()?; body.pop(); body .parse::<u32>() .chain_err(|| ErrorKind::RandomResponseError(body)) } fn run() -> Result<()> { let url = format!("https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain"); let response = reqwest::blocking::get(&url)?; let random_value: u32 = parse_response(response)?; println!("a random number between 0 and 10: {}", random_value); Ok(()) } fn main() { if let Err(error) = run() { match *error.kind() { ErrorKind::Io(_) => println!("Standard IO error: {:?}", error), ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error), ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error), ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error), _ => println!("Other error: {:?}", error), } } }
Obtain backtrace of complex error scenarios
This recipe shows how to handle a complex error scenario and then
print a backtrace. It relies on chain_err
to extend errors by
appending new errors. The error stack can be unwound, thus providing
a better context to understand why an error was raised.
The below recipes attempts to deserialize the value 256
into a
u8
. An error will bubble up from Serde then csv and finally up to the
user code.
use error_chain::error_chain; use serde::Deserialize; use std::fmt; error_chain! { foreign_links { Reader(csv::Error); } } #[derive(Debug, Deserialize)] struct Rgb { red: u8, blue: u8, green: u8, } impl Rgb { fn from_reader(csv_data: &[u8]) -> Result<Rgb> { let color: Rgb = csv::Reader::from_reader(csv_data) .deserialize() .nth(0) .ok_or("Cannot deserialize the first CSV record")? .chain_err(|| "Cannot deserialize RGB color")?; Ok(color) } } impl fmt::UpperHex for Rgb { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hexa = u32::from(self.red) << 16 | u32::from(self.blue) << 8 | u32::from(self.green); write!(f, "{:X}", hexa) } } fn run() -> Result<()> { let csv = "red,blue,green 102,256,204"; let rgb = Rgb::from_reader(csv.as_bytes()).chain_err(|| "Cannot read CSV data")?; println!("{:?} to hexadecimal #{:X}", rgb, rgb); Ok(()) } fn main() { if let Err(ref errors) = run() { eprintln!("Error level - description"); errors .iter() .enumerate() .for_each(|(index, error)| eprintln!("└> {} - {}", index, error)); if let Some(backtrace) = errors.backtrace() { eprintln!("{:?}", backtrace); } // In a real use case, errors should handled. For example: // ::std::process::exit(1); } }
Backtrace error rendered:
Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type
Run the recipe with RUST_BACKTRACE=1
to display a detailed backtrace
associated with this error.