Creating new error types
Let's wrap std library errors with our Errors
use std::fmt; use std::fmt::Debug; use std::num::{ParseIntError, ParseFloatError};
#[derive(Debug)] enum MyErrors { Error1, Error2 }
We also need to implement fmt::Display for our Errors.
impl fmt::Display for MyErrors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MyErrors::Error1 => write!(f, "my first error"), MyErrors::Error2 => write!(f, "my second error"), } } }
Let's implement our own function to test this [1]
fn test_error1() -> Result<(), MyErrors> { let _ = "a".parse::<i32>()?; let _ = "a".parse::<f64>()?; Ok(()) } fn test_error2() -> Result<(), MyErrors> { let _ = "1".parse::<i32>()?; let _ = "a".parse::<f64>()?; Ok(()) }
The above one raises error, because Rust doesn't know how to convert ParseIntError to MyErrors. We need to implement them using From trait.
impl From<ParseIntError> for MyErrors { fn from(err: ParseIntError) -> MyErrors { MyErrors::Error1 } } impl From<ParseFloatError> for MyErrors { fn from(err: ParseFloatError) -> MyErrors { MyErrors::Error2 } }
You can also avoid the above step and convert it explicitly using
let _ = "a".parse::<i32>().map_err(|_| MyErrors::Error1)?;
But, implementing From once and using it everywhere is less verbose.
We can use these in our main function which returns our error type on failure.
fn main() -> Result<(), MyErrors> { //test_error1()?; if let Err(e) = test_error1() { println!("Error message: {}", e) } if let Err(e) = test_error2() { println!("Error message: {}", e) } Ok(()) }
If we want to use std::error::Error in main, we need to implement it for MyErrors
impl std::error::Error for MyErrors{}
Then we can use
fn main() -> Result<(), Box<dyn std::error::Error>> { ... }
Full working example using everything above. Remember if you don't want infallible main, you can just print the error message in main if any, otherwise use ? operator.
Wrapping underlying errors
We can create our error type that can directly wrap underlying error and print backtrace.
#[derive(Debug)] enum MyErrors { Error1(ParseIntError), Error2(ParseFloatError) } // Implement Display impl fmt::Display for MyErrors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MyErrors::Error1(..) => write!(f, "my first error"), // wrap ParseIntError MyErrors::Error2(..) => write!(f, "my second error"), // wrap ParseFloatError } } }
We need to implement From for converting error types from one to another
// convert ParseIntError to MyErrors::Error1 impl From<ParseIntError> for MyErrors { fn from(err: ParseIntError) -> MyErrors { MyErrors::Error1(err) } }
// convert ParseFloatError to MyErrors::Error1 impl From<ParseFloatError> for MyErrors { fn from(err: ParseFloatError) -> MyErrors { MyErrors::Error2(err) } }
We can use the same test functions as above [1] and we need backtrace. For backtrace, instead of empty impl std::error::Error for our error type, we can implement source
impl std::error::Error for MyErrors { // if you want source of the error ---- // In our case display error message related to // ParseIntError or ParseFloatError fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match *self { MyErrors::Error1(ref e) => Some(e), MyErrors::Error2(ref e) => Some(e), } } }
and we have our main function like this
fn main() -> Result<(), Box<dyn std::error::Error>> { // test_error1()?; if let Err(e) = test_error1() { println!("Error message: {}", e); if let Some(source) = e.source() { println!(" Caused by: {}", source); } } Ok(()) }
We get this output
Error message: my first error Caused by: invalid digit found in string
Playground for above -- with boilerplate
To create our own error type, we implemented, From conversions, std::error::Error and it's methods. In order to avoid all these, we can use a trait called thiserror
#[derive(thiserror::Error, Debug)] enum MyErrors { #[error("my first error")] Error1(#[from] ParseIntError), #[error("my second error")] Error2(#[from] ParseFloatError) }
The above implements custom error type with our message, from conversions and backtrace.
Playground for above -- using thiserror
Both of these, produce the same output. Using thiserror reduced the code size by half in our example