基于Result<_,E1>和Result<T,E2>得到Result<T,E>的优雅方法?

问题描述 投票:0回答:1

我用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 作为 Err 我的 Result 可能只是一个黑客。我想

  • 读取,直到任何一个空格字符,不仅仅是 '\n';
  • 避免在每次操作后进行显式的C式错误检查。
  • 使用更好的 "错误类型 "来检查 read_i32().

如何实现呢?Result<i32, ParseIntError> 还不够通用,因为在解析之前就可能出现问题。.map() 和其他功能魔法似乎并没有用,因为它是在 read_line() 连串 .s (在我的例子中)。

validation input error-handling rust optional
1个回答
2
投票

Rust中的错误处理是目前仍在发展的东西。你可能会对最近的这篇文章感兴趣 https:/nick.groenen.mepostsrust-error-handling。 以获得一些通用的、最新的建议和讨论。

根据你想保留多少关于错误的结构化信息,有几种可能的方法。在光谱的一端,你可以使用 这个错误 来构建你自己的精确错误类型。

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());
}

这使得它更容易产生 MyErrors. 这里的缺点是,通过这种方式,你放弃了一些为错误类型添加上下文信息的能力;例如,如果在你的函数中,有多处地方有一个 io::Error 可能会发生,那么使用第一种方法,你的错误类型可以包括多个变体,以精确地识别它发生的位置,以及其他你可能想要添加的上下文信息(如错误发生的文件中的行号);仅仅通过底层的 io::Error.

另一方面,如果你知道使用了 read_i32 不需要任何关于错误类型的结构化信息,你只需要生成人类可读的错误信息,那么就不需要定义自定义的错误类型,你可以使用 反正 这样的箱子,比如说。

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.comrust-langrustissues53487。).

© www.soinside.com 2019 - 2024. All rights reserved.