定义一个错误类型

有时候把所有不同的错误都视为一种错误类型会简化代码。我们将用一个自定义错误类型来 演示这一点。

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())
  • 能够与其他错误很好地整合
  1. use std::error;
  2. use std::fmt;
  3. type Result<T> = std::result::Result<T, DoubleError>;
  4. #[derive(Debug, Clone)]
  5. // 定义我们的错误类型,这种类型可以根据错误处理的实际情况定制。
  6. // 我们可以完全自定义错误类型,也可以在类型中完全采用底层的错误实现,
  7. // 也可以介于二者之间。
  8. struct DoubleError;
  9. // 错误的生成与它如何显示是完全没关系的。没有必要担心复杂的逻辑会导致混乱的显示。
  10. //
  11. // 注意我们没有储存关于错误的任何额外信息,也就是说,如果不修改我们的错误类型定义的话,
  12. // 就无法指明是哪个字符串解析失败了。
  13. impl fmt::Display for DoubleError {
  14. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  15. write!(f, "invalid first item to double")
  16. }
  17. }
  18. // 为 `DoubleError` 实现 `Error` trait,这样其他错误可以包裹这个错误类型。
  19. impl error::Error for DoubleError {
  20. fn source(&self) -> Option<&(dyn error::Error + 'static)> {
  21. // 泛型错误,没有记录其内部原因。
  22. None
  23. }
  24. }
  25. fn double_first(vec: Vec<&str>) -> Result<i32> {
  26. vec.first()
  27. // 把错误换成我们的新类型。
  28. .ok_or(DoubleError)
  29. .and_then(|s| {
  30. s.parse::<i32>()
  31. // 这里也换成新类型。
  32. .map_err(|_| DoubleError)
  33. .map(|i| 2 * i)
  34. })
  35. }
  36. fn print(result: Result<i32>) {
  37. match result {
  38. Ok(n) => println!("The first doubled is {}", n),
  39. Err(e) => println!("Error: {}", e),
  40. }
  41. }
  42. fn main() {
  43. let numbers = vec!["42", "93", "18"];
  44. let empty = vec![];
  45. let strings = vec!["tofu", "93", "18"];
  46. print(double_first(numbers));
  47. print(double_first(empty));
  48. print(double_first(strings));
  49. }