定义一个错误类型
有时候把所有不同的错误都视为一种错误类型会简化代码。我们将用一个自定义错误类型来演示这一点。
Rust 允许我们定义自己的错误类型。一般来说,一个 “好的” 错误类型应当:
- 用同一个类型代表了多种错误
- 向用户提供了清楚的错误信息
- 能够容易地与其他类型比较
- 好的例子:
Err(EmptyVec)
- 坏的例子:
Err("Please use a vector with at least one element".to_owned())
- 好的例子:
- 能够容纳错误的具体信息
- 好的例子:
Err(BadChar(c, position))
- 坏的例子:
Err("+ cannot be used here".to_owned())
- 好的例子:
- 能够与其他错误很好地整合
use std::error; use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; #[derive(Debug, Clone)] // 定义我们的错误类型,这种类型可以根据错误处理的实际情况定制。 // 我们可以完全自定义错误类型,也可以在类型中完全采用底层的错误实现, // 也可以介于二者之间。 struct DoubleError; // 错误的生成与它如何显示是完全没关系的。没有必要担心复杂的逻辑会导致混乱的显示。 // // 注意我们没有储存关于错误的任何额外信息,也就是说,如果不修改我们的错误类型定义的话, // 就无法指明是哪个字符串解析失败了。 impl fmt::Display for DoubleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } // 为 `DoubleError` 实现 `Error` trait,这样其他错误可以包裹这个错误类型。 impl error::Error for DoubleError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { // 泛型错误,没有记录其内部原因。 None } } fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() // 把错误换成我们的新类型。 .ok_or(DoubleError) .and_then(|s| { s.parse::<i32>() // 这里也换成新类型。 .map_err(|_| DoubleError) .map(|i| 2 * i) }) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }