chainerror
chainerror
provides an error backtrace like failure
without doing a real backtrace, so even after you strip
your
binaries, you still have the error backtrace.
chainerror
has no dependencies!
chainerror
uses .source()
of std::error::Error
along with line()!
and file()!
to provide a nice debug error backtrace.
It encapsulates all types, which have Display + Debug
and can store the error cause internally.
Along with the ChainError<T>
struct, chainerror
comes with some useful helper macros to save a lot of typing.
Debug information is worth it!
Now continue reading the Tutorial
Example:
Output:
$ cargo run -q --example example
Main Error Report: func1 error calling func2
Error reported by Func2Error: func2 error: calling func3
The root cause was: std::io::Error: Kind(
NotFound
)
Debug Error:
examples/example.rs:45: func1 error calling func2
Caused by:
examples/example.rs:20: Func2Error(func2 error: calling func3)
Caused by:
examples/example.rs:13: Error reading 'foo.txt'
Caused by:
Kind(NotFound)
use chainerror::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func3() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(())
}
derive_str_cherr!(Func2Error);
fn func2() -> ChainResult<(), Func2Error> {
func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?;
Ok(())
}
enum Func1Error {
Func2,
IO(String),
}
impl ::std::fmt::Display for Func1Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match self {
Func1Error::Func2 => write!(f, "func1 error calling func2"),
Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename),
}
}
}
impl ::std::fmt::Debug for Func1Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self)
}
}
fn func1() -> ChainResult<(), Func1Error> {
func2().map_err(|e| cherr!(e, Func1Error::Func2))?;
let filename = String::from("bar.txt");
do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?;
Ok(())
}
fn main() {
if let Err(e) = func1() {
match e.kind() {
Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
Func1Error::IO(filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename)
}
}
if let Some(e) = e.find_chain_cause::<Func2Error>() {
eprintln!("\nError reported by Func2Error: {}", e)
}
if let Some(e) = e.root_cause() {
let ioerror = e.downcast_ref::<io::Error>().unwrap();
eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror);
}
eprintln!("\nDebug Error:\n{:?}", e);
}
}
Features
no-fileline
: completely turn off storing filename and line
display-cause
: turn on printing a backtrace of the errors in Display
no-debug-cause
: turn off printing a backtrace of the errors in Debug
Simple String Errors
An easy way of doing error handling in rust is by returning String
as a Box<std::error::Error>
.
If the rust main
function returns an Err()
, this Err()
will be displayed with std::fmt::Debug
.
As you can see by running the example (by pressing the "Play" button in upper right of the code block),
this only
prints out the last Error
.
Error: StringError("func1 error")
The next chapters of this tutorial show how chainerror
adds more information
and improves inspecting the sources of an error.
You can also run the tutorial examples in the checked out chainerror git repo.
$ cargo run -q --example tutorial1
use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { if let Err(_) = do_some_io() { Err("func2 error")?; } Ok(()) } fn func1() -> Result<(), Box<Error>> { if let Err(_) = func2() { Err("func1 error")?; } Ok(()) } fn main() -> Result<(), Box<Error>> { func1() }
Simple Chained String Errors
With relatively small changes and the help of the cherr!
macro of the chainerror
crate
the String
errors are now chained together.
Press the play button in the upper right corner and see the nice debug output.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { if let Err(e) = do_some_io() { Err(cherr!(e, "func2 error"))?; } Ok(()) } fn func1() -> Result<(), Box<Error>> { if let Err(e) = func2() { Err(cherr!(e, "func1 error"))?; } Ok(()) } fn main() -> Result<(), Box<Error>> { func1() } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
What did we do here?
if let Err(e) = do_some_io() {
Err(cherr!(e, "func2 error"))?;
}
The macro cherr!(olderror, newerror)
stores olderror
as the source/cause of newerror
along with the filename (file!()
) and line number (line!()
)
and returns newerror
.
Err()?
then returns the inner error applying .into()
, so that we
again have a Err(Box<Error>)
as a result.
The Debug
implementation of ChainError<T>
(which is returned by cherr!()
)
prints the Debug
of T
prefixed with the stored filename and line number.
ChainError<T>
in our case is ChainError<String>
.
Mapping Errors
Now let's get more rust idiomatic by using .map_err()
.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { do_some_io().map_err(|e| cherr!(e, "func2 error"))?; Ok(()) } fn func1() -> Result<(), Box<Error>> { func2().map_err(|e| cherr!(e, "func1 error"))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { eprintln!("{:?}", e); } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
If you compare the output to the previous example, you will see, that:
Error: src/main.rs:19: "func1 error"
changed to just:
src/main.rs:16: "func1 error"
This is, because we caught the error of func1()
in main()
and print it out ourselves.
We can now control, whether to output in Debug
or Display
mode.
Maybe depending on --debug
as a CLI argument.
Saving coding chars
Because decorating an error with more information should not
let you jump through hoops, chainerror
has a quick macro for that.
mstrerror!()
fits right into .map_err()
letting you quickly add
more debug strings.
mstrerror!()
even understands format!()
syntax like println!()
.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box<Error>> { func2().map_err(mstrerr!("func1 error"))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { eprintln!("{:?}", e); } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
The source() of Errors
Sometimes you want to inspect the source()
of an Error
.
chainerror
implements std::error::Error::source()
, so you can get the cause of an error.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box<Error>> { if let Err(e) = func2() { if let Some(s) = e.source() { eprintln!("func2 failed because of '{}'", s); Err(e).map_err(mstrerr!("func1 error"))?; } } Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { eprintln!("{}", e); } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
Note, that because we changed the output of the error in main()
from
Debug
to Display
, we don't see the error backtrace with filename and line number.
To enable the Display
backtrace, you have to enable the feature display-cause
for chainerror
.
Downcast the Errors
std::error::Error
comes with some helper methods to get to the original object of the
&(dyn Error + 'static)
returned by .source()
.
pub fn downcast_ref<T: Error + 'static>(&self) -> Option<&T>
pub fn downcast_mut<T: Error + 'static>(&mut self) -> Option<&mut T>
This is how it looks like, when using those:
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box<Error>> { func2().map_err(mstrerr!("func1 error"))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { eprintln!("Error: {}", e); let mut s = e.as_ref(); while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::<io::Error>() { eprintln!("caused by: std::io::Error: {}", ioerror); match ioerror.kind() { io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), _ => {} } } else { eprintln!("caused by: {}", c); } s = c; } } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
The root cause of all Errors
chainerror
also has some helper methods:
fn is_chain<T: 'static + Display + Debug>(&self) -> bool
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>
fn root_cause(&self) -> Option<&(dyn Error + 'static)>
fn find_cause<U: Error + 'static>(&self) -> Option<&U>
fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>>
fn kind<'a>(&'a self) -> &'a T
Using downcast_chain_ref::<String>()
gives a ChainError<String>
, which can be used
to call .find_cause::<io::Error>()
.
if let Some(s) = e.downcast_chain_ref::<String>() {
if let Some(ioerror) = s.find_cause::<io::Error>() {
or to use .root_cause()
, which of course can be of any type implementing std::error::Error
.
if let Some(e) = s.root_cause() {
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box<Error>> { func2().map_err(mstrerr!("func1 error"))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { eprintln!("Error: {}", e); if let Some(s) = e.downcast_chain_ref::<String>() { if let Some(ioerror) = s.find_cause::<io::Error>() { eprintln!("caused by: std::io::Error: {}", ioerror); match ioerror.kind() { io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), _ => {} } } if let Some(e) = s.root_cause() { let ioerror = e.downcast_ref::<io::Error>().unwrap(); eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); } } } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
Finding an Error cause
To distinguish the errors occuring in various places, we can define named string errors with the "new type" pattern.
derive_str_cherr!(Func2Error);
derive_str_cherr!(Func1Error);
Instead of ChainError<String>
we now have struct Func1Error(String)
and ChainError<Func1Error>
.
In the main
function you can see, how we can match the different errors.
Also see:
if let Some(f2err) = f1err.find_chain_cause::<Func2Error>() {
as a shortcut to
if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
hiding the ChainError<T>
implementation detail.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func1Error); fn func1() -> Result<(), Box<Error>> { func2().map_err(mstrerr!(Func1Error, "func1 error"))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { eprintln!("Func1Error: {}", f1err); if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() { eprintln!("Func2Error: {}", f2err); } if let Some(f2err) = f1err.find_chain_cause::<Func2Error>() { eprintln!("Debug Func2Error:\n{:?}", f2err); } } } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
Selective Error Handling
What about functions returning different Error types?
In this example func1()
can return either Func1ErrorFunc2
or Func1ErrorIO
.
We might want to match
on func1()
with something like:
fn main() -> Result<(), Box<Error>> {
match func1() {
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorIO>() =>
eprintln!("Func1ErrorIO:\n{:?}", s),
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() =>
eprintln!("Func1ErrorFunc2:\n{:?}", s),
Ok(_) => {},
}
Ok(())
}
but this is not valid rust code, so we end up doing it the hard way. In the next chapter, we will see, how to solve this more elegantly.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func1ErrorFunc2); derive_str_cherr!(Func1ErrorIO); fn func1() -> Result<(), Box<Error>> { func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?; let filename = "bar.txt"; do_some_io().map_err(mstrerr!(Func1ErrorIO, "Error reading '{}'", filename))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { if let Some(s) = e.downcast_ref::<ChainError<Func1ErrorIO>>() { eprintln!("Func1ErrorIO:\n{:?}", s); } if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() { eprintln!("Func1ErrorFunc2:\n{:?}", s); } } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
ErrorKind to the rescue
To cope with different kind of errors, we introduce the kind of an error Func1ErrorKind
with an enum.
Because we derive Debug
and implement Display
our Func1ErrorKind
enum, this enum can be used as
a std::error::Error
.
Not using String
errors anymore, the cherr!()
macro seen in the beginning of
the tutorial has to be used again.
Only returning Func1ErrorKind
in func1()
now let us get rid of Result<(), Box<Error>>
and we can
use ChainResult<(), Func1ErrorKind>
.
In main
we can now directly use the methods of ChainError<T>
without downcasting the error first.
Also a nice match
on ChainError<T>.kind()
is now possible, which returns &T
, meaning
&Func1ErrorKind
here.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } #[derive(Debug)] enum Func1ErrorKind { Func2, IO(String), } impl ::std::fmt::Display for Func1ErrorKind { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match self { Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), } } } fn func1() -> ChainResult<(), Func1ErrorKind> { func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::IO(filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename) } } if let Some(e) = e.find_chain_cause::<Func2Error>() { eprintln!("\nError reported by Func2Error: {}", e) } eprintln!("\nDebug Error:\n{:?}", e); } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
Debug for the ErrorKind
One small improvement at the end of the tutorial is to fix the debug output of
Func1ErrorKind
. As you probably noticed, the output doesn't say much of the enum.
Debug Error:
src/main.rs:35: Func2
[…]
As a lazy shortcut, we implement Debug
by calling Display
and end up with
Debug Error:
src/main.rs:40: func1 error calling func2
[…}
which gives us a lot more detail.
use crate::chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box<Error>> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } enum Func1ErrorKind { Func2, IO(String), } impl ::std::fmt::Display for Func1ErrorKind { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match self { Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), } } } impl ::std::fmt::Debug for Func1ErrorKind { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self) } } fn func1() -> ChainResult<(), Func1ErrorKind> { func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; Ok(()) } fn main() -> Result<(), Box<Error>> { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::IO(filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename) } } if let Some(e) = e.find_chain_cause::<Func2Error>() { eprintln!("\nError reported by Func2Error: {}", e) } eprintln!("\nDebug Error:\n{:?}", e); } Ok(()) } # #[allow(dead_code)] # mod chainerror { # /*! # # `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your # binaries, you still have the error backtrace. # # `chainerror` has no dependencies! # # `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. # It encapsulates all types, which have `Display + Debug` and can store the error cause internally. # # Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # # ## Features # # `no-fileline` # : completely turn off storing filename and line # # `display-cause` # : turn on printing a backtrace of the errors in `Display` # # `no-debug-cause` # : turn off printing a backtrace of the errors in `Debug` # # # # Tutorial # # Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # # # Examples # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # # ~~~rust # use chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func3() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; # Ok(()) # } # # enum Func1Error { # Func2, # IO(String), # } # # impl ::std::fmt::Display for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1Error::Func2 => write!(f, "func1 error calling func2"), # Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } # # impl ::std::fmt::Debug for Func1Error { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self) # } # } # # fn func1() -> ChainResult<(), Func1Error> { # func2().map_err(|e| cherr!(e, Func1Error::Func2))?; # let filename = String::from("bar.txt"); # do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # assert!( # match e.kind() { # Func1Error::Func2 => { # eprintln!("Main Error Report: func1 error calling func2"); # true # } # Func1Error::IO(filename) => { # eprintln!("Main Error Report: func1 error reading '{}'", filename); # false # } # } # ); # # assert!(e.find_chain_cause::<Func2Error>().is_some()); # # if let Some(e) = e.find_chain_cause::<Func2Error>() { # eprintln!("\nError reported by Func2Error: {}", e) # } # # # assert!(e.root_cause().is_some()); # # if let Some(e) = e.root_cause() { # let ioerror = e.downcast_ref::<io::Error>().unwrap(); # eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); # } # # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:47: func1 error calling func2 # Caused by: # src/lib.rs:22: Func2Error(func2 error: calling func3) # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } # # else { # # unreachable!(); # # } # } # ~~~ # # !*/ # # use std::any::TypeId; # use std::error::Error; # use std::fmt::{Debug, Display, Formatter, Result}; # # /** chains an inner error kind `T` with a causing error # **/ # pub struct ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # occurrence: Option<(u32, &'static str)>, # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # } # # /// convenience type alias # pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; # # impl<T: 'static + Display + Debug> ChainError<T> { # #[cfg(not(feature = "no-fileline"))] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { # occurrence, # kind, # error_cause, # } # } # # #[cfg(feature = "no-fileline")] # /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly # pub fn new( # kind: T, # error_cause: Option<Box<dyn Error + 'static>>, # _occurrence: Option<(u32, &'static str)>, # ) -> Self { # Self { kind, error_cause } # } # # /// return the root cause of the error chain, if any exists # pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { # let mut cause = self as &(dyn Error + 'static); # while let Some(c) = cause.source() { # cause = c; # } # Some(cause) # } # # /** find the first error cause of type U, if any exists # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<io::Error>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # } # # else { # # panic!(); # # } # } # # else { # # unreachable!(); # # } # } # ~~~ # **/ # pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<U>() { # return cause.downcast_ref::<U>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** find the first error cause of type ChainError<U>, if any exists # # Same as `find_cause`, but hides the `ChainError<U>` implementation internals # # # Examples # # ~~~rust,ignore # /// Instead of writing # err.find_cause::<ChainError<FooError>>(); # # /// leave out the ChainError<T> implementation detail # err.find_chain_cause::<FooError>(); # ~~~ # # **/ # pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { # let mut cause = self as &(dyn Error + 'static); # loop { # if cause.is::<ChainError<U>>() { # return cause.downcast_ref::<ChainError<U>>(); # } # # match cause.source() { # Some(c) => cause = c, # None => return None, # } # } # } # # /** return a reference to T of `ChainError<T>` # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # fn do_some_io() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # #[derive(Debug)] # enum Func1ErrorKind { # Func2, # IO(String), # } # # // impl ::std::fmt::Display for Func1ErrorKind {…} # # impl ::std::fmt::Display for Func1ErrorKind { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # # } # # } # # } # # fn func1() -> ChainResult<(), Func1ErrorKind> { # func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; # do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; # Ok(()) # } # # fn main() { # if let Err(e) = func1() { # match e.kind() { # Func1ErrorKind::Func2 => {}, # Func1ErrorKind::IO(filename) => panic!(), # } # } # # else { # # unreachable!(); # # } # } # ~~~ # # **/ # pub fn kind<'a>(&'a self) -> &'a T { # &self.kind # } # } # # /** convenience trait to hide the `ChainError<T>` implementation internals # **/ # pub trait ChainErrorDown { # /** test if of type `ChainError<T>` # **/ # fn is_chain<T: 'static + Display + Debug>(&self) -> bool; # /** downcast to a reference of `ChainError<T>` # **/ # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; # /** downcast to a mutable reference of `ChainError<T>` # **/ # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; # } # # impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # TypeId::of::<T>() == TypeId::of::<U>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } # } else { # None # } # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # if self.is_chain::<T>() { # unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } # } else { # None # } # } # } # # impl ChainErrorDown for dyn Error + 'static { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl ChainErrorDown for dyn Error + 'static + Send + Sync { # fn is_chain<T: 'static + Display + Debug>(&self) -> bool { # self.is::<ChainError<T>>() # } # # fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { # self.downcast_ref::<ChainError<T>>() # } # # fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { # self.downcast_mut::<ChainError<T>>() # } # } # # impl<T: 'static + Display + Debug> Error for ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { # fn source(&self) -> Option<&(dyn Error + 'static)> { # if let Some(ref e) = self.error_cause { # Some(e.as_ref()) # } else { # None # } # } # } # # impl<T: 'static + Display + Debug> Display for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # write!(f, "{}", self.kind)?; # # #[cfg(feature = "display-cause")] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Display::fmt(&e, f)?; # } # } # Ok(()) # } # } # # impl<T: 'static + Display + Debug> Debug for ChainError<T> { # fn fmt(&self, f: &mut Formatter) -> Result { # #[cfg(not(feature = "no-fileline"))] # { # if let Some(o) = self.occurrence { # write!(f, "{}:{}: ", o.1, o.0)?; # } # } # # if self.is_chain::<String>() { # Display::fmt(&self.kind, f)?; # } else { # Debug::fmt(&self.kind, f)?; # } # # #[cfg(not(feature = "no-debug-cause"))] # { # if let Some(e) = self.source() { # writeln!(f, "\nCaused by:")?; # Debug::fmt(&e, f)?; # } # } # Ok(()) # } # } # # /** creates a new `ChainError<T>` # # # Examples # # Create a new ChainError<FooError>, where `FooError` must implement `Display` and `Debug`. # ~~~rust # # use chainerror::*; # # # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # // impl ::std::fmt::Display for FooError # # fn do_some_stuff() -> bool { # false # } # # fn func() -> ChainResult<(), FooError> { # if ! do_some_stuff() { # Err(cherr!(FooError::Baz("Error")))?; # } # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # Additionally an error cause can be added. # # ~~~rust # # use chainerror::*; # # use std::io; # # use std::error::Error; # # # # #[derive(Debug)] # # enum FooError { # # Bar, # # Baz(&'static str), # # } # # # # impl ::std::fmt::Display for FooError { # # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # # match self { # # FooError::Bar => write!(f, "Bar Error"), # # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # # } # # } # # } # # # fn do_some_stuff() -> Result<(), Box<Error>> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # # fn func() -> ChainResult<(), FooError> { # do_some_stuff().map_err( # |e| cherr!(e, FooError::Baz("Error")) # )?; # Ok(()) # } # # # # pub fn main() { # # match func().unwrap_err().kind() { # # FooError::Baz(s) if s == &"Error" => {}, # # _ => panic!(), # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! cherr { # ( $k:expr ) => { # ChainError::<_>::new($k, None, Some((line!(), file!()))) # }; # ( $e:expr, $k:expr ) => { # ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) # }; # } # # /** convenience macro for |e| cherr!(e, format!(…)) # # # Examples # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; # Ok(()) # } # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!("func1 error"))?; # Ok(()) # } # # # fn main() { # # if let Err(e) = func1() { # # assert_eq!( # # format!("\n{:?}\n", e), r#" # # src/lib.rs:20: func1 error # # Caused by: # # src/lib.rs:15: Error reading 'foo.txt' # # Caused by: # # Kind(NotFound) # # "# # # ); # # } else { # # unreachable!(); # # } # # } # ~~~ # # `mstrerr!()` can also be used to map a new `ChainError<T>`, where T was defined with # `derive_str_cherr!(T)` # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> Result<(), Box<Error>> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # **/ # #[macro_export] # macro_rules! mstrerr { # ( $t:ident, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $t:path, $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, $t (format!($v, $( $more , )* ))) # }; # ( $v:expr $(, $more:expr)* ) => { # |e| cherr!(e, format!($v, $( $more , )* )) # }; # } # # /** convenience macro to create a "new type" T(String) and implement Display + Debug for T # # ~~~rust # # use crate::chainerror::*; # # use std::error::Error; # # use std::io; # # use std::result::Result; # # # # fn do_some_io() -> Result<(), Box<Error>> { # # Err(io::Error::from(io::ErrorKind::NotFound))?; # # Ok(()) # # } # # # derive_str_cherr!(Func2Error); # # fn func2() -> ChainResult<(), Func2Error> { # let filename = "foo.txt"; # do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; # Ok(()) # } # # derive_str_cherr!(Func1Error); # # fn func1() -> Result<(), Box<Error>> { # func2().map_err(mstrerr!(Func1Error, "func1 error"))?; # Ok(()) # } # # # # fn main() { # # if let Err(e) = func1() { # # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { # # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); # # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); # # } else { # # panic!(); # # } # # } else { # # unreachable!(); # # } # # } # ~~~ # # **/ # #[macro_export] # macro_rules! derive_str_cherr { # ($e:ident) => { # struct $e(String); # impl ::std::fmt::Display for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}", self.0) # } # } # impl ::std::fmt::Debug for $e { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # write!(f, "{}({})", stringify!($e), self.0) # } # } # impl ::std::error::Error for $e {} # }; # } # }
The End
That's it for now…
Happy error handling!
To report issues, submit pull request or for the source code, examples and the book source, visit the Git Repo.