Rust 中公共字段自定义错误的最佳策略
Best strategy for custom errors with common fields in Rust
我已经实现了a parser in Rust that can return different types of errors。以前我使用不同的结构来实现它,而没有实现 std::error::Error
特性的字段。问题是我遇到了两个问题:
- 解析函数 returns a
Box<dyn Error>
,我发现它在检查返回的错误时做作了,因为我必须回退到 .downcast_ref::<SomeCustomError>()
方法而不是简单地能够使用匹配并检查返回的错误变体。
- 现在我要报告所有错误,带有自定义消息,line number 和 position 发生错误的文本。这一项用Structs很容易解决,但我还是陷入了之前的问题
出于这两个原因,我想用枚举来实现它。 我的问题是:知道所有变体都有字段 pos、line 和 message,我是否应该为所有 Enum 变体重复这三个字段?还有其他更好的方法来表示问题吗?
我想到的是以下代码块,但老实说,必须为所有变体重复相同的代码似乎有点牵强(也许这可以解决宏?):
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),
}
}
}
}
另一种选择是使用 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),
},
}
}
但我也遇到了为每个变体重复代码的问题。
也许我过于简单化了,但我想到了两种方法:
使用struct
如下:
struct MyErr {
line: usize,
pos: usize,
actual_err: MyCustomError,
}
那么,你还可以match
上场actual_err
impl fmt::Display for MyCustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write(f, "{} ", match self.actual_err {
ErrorOne => "Error 1 Message",
ErrorTwo => "Error 2 Message",
})?;
write!(
f,
"at line {} position {}",
self.line, self.pos
)
}
}
}
您实际上可以尝试通过宏生成您的错误变体和关联的 match
语句。或者查看 enum_dispatch
可以为您节省一些样板文件。
我已经实现了a parser in Rust that can return different types of errors。以前我使用不同的结构来实现它,而没有实现 std::error::Error
特性的字段。问题是我遇到了两个问题:
- 解析函数 returns a
Box<dyn Error>
,我发现它在检查返回的错误时做作了,因为我必须回退到.downcast_ref::<SomeCustomError>()
方法而不是简单地能够使用匹配并检查返回的错误变体。 - 现在我要报告所有错误,带有自定义消息,line number 和 position 发生错误的文本。这一项用Structs很容易解决,但我还是陷入了之前的问题
出于这两个原因,我想用枚举来实现它。 我的问题是:知道所有变体都有字段 pos、line 和 message,我是否应该为所有 Enum 变体重复这三个字段?还有其他更好的方法来表示问题吗?
我想到的是以下代码块,但老实说,必须为所有变体重复相同的代码似乎有点牵强(也许这可以解决宏?):
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),
}
}
}
}
另一种选择是使用 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),
},
}
}
但我也遇到了为每个变体重复代码的问题。
也许我过于简单化了,但我想到了两种方法:
使用
struct
如下:struct MyErr { line: usize, pos: usize, actual_err: MyCustomError, }
那么,你还可以
match
上场actual_err
impl fmt::Display for MyCustomError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write(f, "{} ", match self.actual_err { ErrorOne => "Error 1 Message", ErrorTwo => "Error 2 Message", })?; write!( f, "at line {} position {}", self.line, self.pos ) } } }
您实际上可以尝试通过宏生成您的错误变体和关联的
match
语句。或者查看enum_dispatch
可以为您节省一些样板文件。