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 特性的字段。问题是我遇到了两个问题:

  1. 解析函数 returns a Box<dyn Error>,我发现它在检查返回的错误时做作了,因为我必须回退到 .downcast_ref::<SomeCustomError>() 方法而不是简单地能够使用匹配并检查返回的错误变体。
  2. 现在我要报告所有错误,带有自定义消息,line numberposition 发生错误的文本。这一项用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),
        },
    }
}

但我也遇到了为每个变体重复代码的问题。

也许我过于简单化了,但我想到了两种方法:

  1. 使用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
          )
        }
     } 
    }
    
  2. 您实际上可以尝试通过宏生成您的错误变体和关联的 match 语句。或者查看 enum_dispatch 可以为您节省一些样板文件。