validation - 一种基于Result <_,E1>和Result <T,E2>来获取Result <T,E>的好方法吗?

标签 validation input error-handling rust optional

我已经在Rust中编写了一个“程序”以方便地从控制台读取整数:

fn read_i32() -> Result<i32, String> {
  let mut input = String::new();
  match std::io::stdin().read_line(&mut input) {
    Ok(_) => match input.trim_end().parse::<i32>() {
      Ok(integer) => Ok(integer),
      Err(_) => Err(String::from("parsing failed"))
    },
    Err(_) => Err(String::from("reading failed"))
  }
}

fn main() {
  println!("{:?}", read_i32());
}

但是,我正在使用的错误处理显然很差(来自C++,我习惯于异常),并将String用作ErrResult版本可能只是一个hack。我想要
  • 读取直到任何空格字符,不仅是'\n'
  • 避免在每次操作后进行显式的C样式错误检查;
  • read_i32()使用更好的“错误类型”。

  • 如何实现? Result<i32, ParseIntError>不够通用,因为问题可能在解析之前发生。 .map()和其他功能魔术对于通过read_line()链(在我的情况下)在.之后立即启动似乎没有用。

    最佳答案

    Rust中的错误处理现在仍在发展。您可能对这篇最近的文章https://nick.groenen.me/posts/rust-error-handling/感兴趣,以获取一些常规的最新建议和讨论。

    根据要保留的有关错误的结构化信息的数量,有几种可能的方法。在频谱的一端,您可以使用thiserror构造自己的精确错误类型:

    use thiserror::Error;
    
    #[derive(Error, Debug)]
    pub enum MyError {
        #[error("Parsing failed")]
        ParseError { source: std::num::ParseIntError },
    
        #[error("Reading failed")]
        ReadError { source: std::io::Error },
    }
    
    fn read_i32() -> Result<i32, MyError> {
        let mut input = String::new();
        match std::io::stdin().read_line(&mut input) {
            Ok(_) => match input.trim_end().parse::<i32>() {
                Ok(integer) => Ok(integer),
                Err(e) => Err(MyError::ParseError { source: e }),
            },
            Err(e) => Err(MyError::ReadError { source: e }),
        }
    }
    
    fn main() {
        println!("{:?}", read_i32());
    }
    

    再次使用thiserror的另一种方法是,使用#[from]将源错误直接嵌入到您的错误类型中。这使您可以使用?运算符自动将其转换为错误类型:
    use thiserror::Error;
    
    #[derive(Error, Debug)]
    pub enum MyError {
        #[error(transparent)]
        IOError(#[from] std::io::Error),
    
        #[error(transparent)]
        ParseIntError(#[from] std::num::ParseIntError),
    }
    
    fn read_i32() -> Result<i32, MyError> {
        let mut input = String::new();
        std::io::stdin().read_line(&mut input)?;
        let x = input.trim_end().parse::<i32>()?;
        Ok(x)
    }
    
    fn main() {
        println!("{:?}", read_i32());
    }
    

    这样可以更轻松地生成MyError。这样做的缺点是,您这样做会放弃一些将上下文信息添加到错误类型的功能。例如,如果函数中存在多个可能会出现io::Error的位置,则采用第一种方法时,您的错误类型可能会包含多个变体以准确识别它的发生位置,以及可能要添加的其他上下文信息(例如文件中发生错误的行号);仅通过基础io::Error就会失去此功能。

    另一方面,如果您知道使用read_i32的代码将不需要任何有关错误类型的结构化信息,并且只需要生成人类可读的错误消息,则无需定义自定义错误类型,您可以像这样使用anyhow crate ,例如:
    use anyhow::{Context, Result};
    
    fn read_i32() -> Result<i32> {
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).context("Read failed")?;
        let x = input.trim_end().parse::<i32>().context("Parse failed")?;
        Ok(x)
    }
    
    fn main() {
        println!("{:?}", read_i32());
    }
    

    另一方面,如果您甚至不需要那些人类可读的消息(“读取失败”,“解析失败”),那么您也可以将所有错误都转换为Box<dyn Error>:
    use std::error::Error;
    
    fn read_i32() -> Result<i32, Box<dyn Error>> {
        let mut input = String::new();
        std::io::stdin().read_line(&mut input)?;
        let x = input.trim_end().parse::<i32>()?;
        Ok(x)
    }
    
    fn main() {
        println!("{:?}", read_i32());
    }
    

    但是,随着程序的增长,这可能会使错误更难以解释,因为通过这种方式,它们不会提供太多上下文。现在有点不方便的一件事是,从Rust错误中获取回溯信息并不容易(至少在稳定的Rust中不是这样)。这是当前正在处理的(https://github.com/rust-lang/rust/issues/53487)。

    关于validation - 一种基于Result <_,E1>和Result <T,E2>来获取Result <T,E>的好方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62375381/

    相关文章:

    c# - Visual Studio 编译时 SQL 验证

    javascript - onchange jquery 文件类型验证

    python - 从 python tkinter 中的复选框获取输入?

    java - 扫描仪忽略某些输入 (System.in)

    在启动时设置值时,jQuery 验证器 "required"不起作用

    php - Laravel 字符串验证以允许空字符串

    html - 如何动态调整输入标签的大小

    Android setError ("error") 在 Textview 中不起作用

    javascript - Express.js CORS配置,PUT不起作用?

    python - 如何使用更多信息记录 APScheduler 警告?