自定义错误类型
你会学到什么
- 用枚举把领域内的多种失败原因建模成一个错误类型。
- 实现
Display提供人类可读的信息,实现std::error::Error接入生态。 - 用
map_err把底层错误转换成自己的错误。
最小示例
#[derive(Debug)]
enum ParseError {
Empty,
NotANumber(String),
}
运行代码
cd examples
cargo run -p rt_15_custom_errors
cargo test -p rt_15_custom_errors
代码讲解
实现 Display 后,错误可以被友好地打印;实现空的 Error trait 让它能放进 Box<dyn Error>:
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseError::Empty => write!(f, "输入为空"),
ParseError::NotANumber(token) => write!(f, "不是数字: {token}"),
}
}
}
impl std::error::Error for ParseError {}
用 map_err 把标准库错误转换成自己的语义错误:
let value: i64 = trimmed
.parse()
.map_err(|_| ParseError::NotANumber(trimmed.to_string()))?;
常见错误
只 #[derive(Debug)] 而不实现 Display,错误就只能用 {:?} 打印,对用户不友好。生产代码两者通常都要。
实际项目中常用
thiserror自动派生这些实现,我们会在“生态库”阶段介绍。
练习
- 给
ParseError加一个TooLarge(i64)变体并在解析时使用。 - 实现
From<std::num::ParseIntError> for ParseError,然后用?替换map_err。
小结
自定义错误类型让失败原因变得清晰、可匹配。Display + Error 是接入 Rust 错误生态的标准组合。
下一步
接下来进入泛型与 trait 阶段,学习写出可复用的抽象代码。
完整示例代码
下面是 examples/15_custom_errors/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/15_custom_errors/src/main.rs
//! 自定义错误类型:实现 Display、Error 和 From。
use std::fmt;
#[derive(Debug, PartialEq)]
enum ParseError {
Empty,
NotANumber(String),
NotPositive(i64),
}
impl fmt::Display for ParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Empty => write!(formatter, "输入为空"),
ParseError::NotANumber(token) => write!(formatter, "不是数字: {token}"),
ParseError::NotPositive(value) => write!(formatter, "必须为正数: {value}"),
}
}
}
impl std::error::Error for ParseError {}
/// 解析一个正整数,失败时返回带语义的自定义错误。
fn parse_positive(input: &str) -> Result<u32, ParseError> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(ParseError::Empty);
}
let value: i64 = trimmed
.parse()
.map_err(|_| ParseError::NotANumber(trimmed.to_string()))?;
if value <= 0 {
return Err(ParseError::NotPositive(value));
}
Ok(value as u32)
}
fn main() {
for input in ["42", "", "abc", "-5"] {
match parse_positive(input) {
Ok(value) => println!("{input:?} => {value}"),
Err(error) => println!("{input:?} => 错误: {error}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_positive() {
assert_eq!(parse_positive("42"), Ok(42));
assert_eq!(parse_positive(" 7 "), Ok(7));
}
#[test]
fn rejects_bad_input() {
assert_eq!(parse_positive(""), Err(ParseError::Empty));
assert_eq!(
parse_positive("abc"),
Err(ParseError::NotANumber("abc".to_string()))
);
assert_eq!(parse_positive("-5"), Err(ParseError::NotPositive(-5)));
}
#[test]
fn error_displays_message() {
assert_eq!(ParseError::Empty.to_string(), "输入为空");
}
} examples/15_custom_errors/Cargo.toml
[package]
name = "rt_15_custom_errors"
version.workspace = true
edition.workspace = true
publish.workspace = true