Rust - thiserror
This chapter presents usage of thiserror
crate.
Objectives
- Implement a chain of functions where one calls another.
- Return an error from the innermost function.
- Add context to the errors on each subsequent level.
- Handle the error on the top level by displaying all the causes.
- Consider
std::io::Error
as well as user-defined error as a root cause.
Implementation
// config.rs
use std::fmt::Debug;
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
#[error("Failed to read config file")]
FileReadError(#[from] std::io::Error),
#[error("Failed to parse config file: {0}")]
ParseConfigError(String),
}
pub fn load_config() -> Result<(), ConfigError> {
let _text = std::fs::read_to_string("myconfig")?;
Err(ConfigError::ParseConfigError("Unknown key 'foo'".to_string()))?;
return Ok(());
}
// setup.rs
use std::fmt::Debug;
use crate::config;
#[derive(thiserror::Error, Debug)]
pub enum SetupError {
#[error("Failed to configure")]
LoadConfigError(#[from] config::ConfigError),
}
pub fn setup_app() -> Result<(), SetupError> {
Ok(config::load_config()?)
}
// launcher.rs
use std::fmt::Debug;
use std::error::Error;
use crate::setup;
#[derive(thiserror::Error)]
pub enum LaunchError {
#[error("Failed to setup application")]
SetupAppError(#[from] setup::SetupError),
}
impl Debug for LaunchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)?;
let mut err_obj : &dyn Error = self;
while let Some(source) = err_obj.source() {
write!(f, "\n Because: {}", source)?;
err_obj = source;
}
Ok(())
}
}
pub fn launch_app() -> Result<(), LaunchError> {
Ok(setup::setup_app()?)
}
// main.rs
mod config;
mod setup;
mod launcher;
fn show_err_stack(top_err: &dyn std::error::Error) {
eprintln!("{}", top_err);
#[cfg(debug_assertions)]
{
let mut err_obj : &dyn std::error::Error = top_err;
while let Some(source) = err_obj.source() {
eprintln!(" Because: {}", source);
err_obj = source;
}
}
}
fn main() {
if let Err(e) = launcher::launch_app() {
//println!("{}", e); // show only top err message
//println!("{:?}", e); // show all the errors
show_err_stack(&e); // show all the errors (customized)
std::process::exit(1);
}
}
Testing
$ cargo build
$ ./target/debug/thiserror_demo
Failed to setup application
Because: Failed to configure
Because: Failed to read config file
Because: No such file or directory (os error 2)
$ touch myconfig
$ ./target/debug/thiserror_demo
Failed to setup application
Because: Failed to configure
Because: Failed to parse config file: Unknown key 'foo'
Open Topics
- add
impl Debug for T
for each error that derive(DebugStack) - add config file path to
FileReadError