I have implemented a parser in Rust that can return different types of errors. Previously I had implemented it using different Structs without fields that implemented the std::error::Error
trait. The issue is that I encountered two problems:
- The parse function returns a
Box<dyn Error>
, which I find stilted to check what error was returned since I have to fall back to the.downcast_ref::<SomeCustomError>()
method instead of simply being able to use a match and check what error variant was returned. - Now I want all errors to report, with a custom message, line number, and position of the text where the error occurs. This item is easily solved with Structs, but I still fall into the previous problem.
For both reasons is that I want to implement it with Enums. My question is: knowing that all variants have the fields pos, line, and message, should I repeat the three fields for all Enum variants? Is there another better way to represent the problem?
What I had in mind was the following code block, but honestly having to repeat the same code for all variants seems a bit far-fetched (maybe this could be solved with a macro?):
use std::fmt;
enum MyCustomError {
ErrorOne(usize, usize, String),
ErrorTwo(usize, usize, String)
}
impl fmt::Display for MyCustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
MyCustomError::ErrorOne(pos, line, message) => {
write!(
f,
"{} at line {} position {}",
message, line, pos
)
},
MyCustomError::ErrorTwo(pos, line, message) => {
write!(
f,
"{} at line {} position {}",
message, line, pos
)
},
}
}
}
fn throw_error() -> Result<(), MyCustomError> {
Err(MyCustomError::ErrorOne(1, 22, String::from("Error one occurred")))
}
fn main() {
// Now I can simply use match! :D
match throw_error() {
Ok(()) => println!("It works!"),
Err(err) => {
match err {
MyCustomError::ErrorOne(..) => println!("Error one -> {}!", err),
MyCustomError::ErrorTwo(..) => println!("Error two -> {}!", err),
}
}
}
}
Another alternative could be using Snafu:
use snafu::Snafu;
#[derive(Debug, Snafu)]
enum MyEnum {
#[snafu(display("{} at line {} position {}", message, line, pos))]
ErrorOne {
pos: usize,
line: usize,
message: String,
},
#[snafu(display("{} at line {} position {}", message, line, pos))]
ErrorTwo {
pos: usize,
line: usize,
message: String,
},
}
fn throw_error() -> Result<(), MyEnum> {
Err(MyEnum::ErrorOne {
line: 1,
pos: 22,
message: String::from("Error one occurred"),
})
}
fn main() {
// Now I can simply use match! :D
match throw_error() {
Ok(()) => println!("It works!"),
Err(err) => match err {
MyEnum::ErrorOne { .. } => println!("Error one -> {}!", err),
MyEnum::ErrorTwo { .. } => println!("Error two -> {}!", err),
},
}
}
But I fall into the same problem of repeating the code for each variant.