“?;”的正确使用方法具有多种错误类型的语法?

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

我发现自己的函数使用了不同类型的错误,其中一些来自

std
(或任何地方),其中一些是我的。我想使用
?
运算符来减少一般的开销,但是当我有不同的类型时,这很棘手。

在下面的代码中,

foo
可能会因
std::io::Error
错误或
MyError
而失败。

use std::fs::File;
pub enum MyError {
    IO(std::io::Error),
    Custom(String),
}
impl From<std::io::Error> for MyError {
    fn from(value: std::io::Error) -> Self {
        MyError::IO(value)
    }
}

fn bar(fail: bool) -> Result<usize, MyError> {
    if fail {
        Err(MyError::Custom("Was told to fail".to_owned()))
    } else {
        Ok(42)
    }
}
pub fn foo(path: &str, fail: bool) -> Result<usize, MyError> {
    let file: File = File::open(path)?;
    let b: usize = bar(fail)?;
    Ok(b)
}

我可以通过让

MyError
成为一个带有包裹
std::io::Error
的分支的枚举来使其工作,但我不喜欢这种方法。我还尝试使
MyError
成为具有
std::io::Error
实现的特征,但这不起作用,因为返回类型是动态的,我必须搞乱装箱/拆箱。

我不喜欢我的解决方案有几个原因,其中最重要的是,随着更多错误类型的出现,它的扩展性似乎会很差。例如,假设我还有可以抛出

std::fmt::Error
的函数,可能与其他两个组合。如果我只是将其添加到
MyError
中,则调用
foo
并尝试解决错误的函数看起来
foo
可能会因
fmt
错误而失败,但事实并非如此。但是,如果我为每种组合制作不同版本的
MyError
,情况会更糟 - 我需要针对每种错误组合使用不同的
MyError
实现。

(最重要的是,由于额外的包装,它看起来很笨重,而且事实上,它是针对似乎常见问题的手动实现。)

有没有更好的方法来解决这个问题?我真的想说,

foo
可能会失败,其结果可能是一组静态已知的不同错误之一,理想情况下同时保持简洁的
?
语法。

我看到了 合并两种错误类型最惯用的方法是什么?,这似乎并不令人鼓舞,但我的问题有点具体,我很好奇在接下来的几年里是否出现了更好的解决方案。

rust
1个回答
3
投票

一般有两种方法可以解决这个问题。

  • 一个
    enum
  • Error
    特质抽象

enum
方式

优点是可以保留类型信息;缺点是您需要手动指定每种可能的类型。

这是您目前拥有的解决方案。虽然我不建议手动实现其特征,但我宁愿推荐或多或少的非官方标准板条箱

thiserror
:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("IO Error")]
    IO(#[from] std::io::Error),
    #[error("Other Error")]
    Custom(String),
}

注意

#[from]
标签,它将自动为您生成
From
impl。


Error
特质抽象方式

每个错误都应该实现

Error
特征;如果是这样,有几种方法可以抽象它们。

内置方式是

Box<dyn Error>
,尽管它有一些缺点。最值得注意的是,它只允许传递错误,但在许多情况下,您希望在错误通过调用堆栈时添加更多信息。此外,您还会丢失有关错误实际类型的所有信息。因此,这种方法主要推荐用于快速原型设计,它可能不是生产的正确方法。

这是

Box<dyn Error>
版本的外观:

use std::{error::Error, fs::File};

fn bar(fail: bool) -> Result<usize, String> {
    if fail {
        Err("Was told to fail".to_owned())
    } else {
        Ok(42)
    }
}
pub fn foo(path: &str, fail: bool) -> Result<usize, Box<dyn Error>> {
    let file: File = File::open(path)?;
    let b: usize = bar(fail)?;
    Ok(b)
}

更复杂的方法是使用包装箱,例如

anyhow
miette

这是一个

anyhow
解决方案:

use anyhow::{anyhow, Result};
use std::fs::File;

fn bar(fail: bool) -> Result<usize> {
    if fail {
        Err(anyhow!("Was told to fail"))
    } else {
        Ok(42)
    }
}
pub fn foo(path: &str, fail: bool) -> Result<usize> {
    let file: File = File::open(path)?;
    let b: usize = bar(fail)?;
    Ok(b)
}

我个人最喜欢的

就我个人而言,在我的项目中,通常会结合使用

thiserror
miette
。这允许您在需要知道确切类型时使用
enums
,但也允许您使用
Error
类型进行抽象。

我真的很喜欢

miette
,因为有了
fancy
功能,它可以为您提供非常好的分层错误消息。

这里有两种方法可以实现您在

miette
中的情况:

  • miette
    enum
    方式:
use std::fs::File;
use thiserror::Error;

use miette::{Diagnostic, Result};

#[derive(Error, Diagnostic, Debug)]
enum MyError {
    #[error("IO Error")]
    IO(#[from] std::io::Error),
    #[error("Other Error")]
    Custom(String),
}

fn bar(fail: bool) -> Result<usize, MyError> {
    if fail {
        Err(MyError::Custom("Was told to fail".to_string()))
    } else {
        Ok(42)
    }
}

fn foo(path: &str, fail: bool) -> Result<usize, MyError> {
    let file: File = File::open(path)?;
    let b: usize = bar(fail)?;
    Ok(b)
}

fn main() -> Result<()> {
    let value = foo("does_not_exist.txt", false)?;
    println!("{}", value);

    Ok(())
}
Error:   × IO Error
  ╰─▶ The system cannot find the file specified. (os error 2)
  • miette
    Error
    抽象方式:
use std::fs::File;

use miette::{bail, Context, IntoDiagnostic, Result};

fn bar(fail: bool) -> Result<usize> {
    if fail {
        bail!("Was told to fail")
    }

    Ok(42)
}

fn foo(path: &str, fail: bool) -> Result<usize> {
    let _file: File = File::open(path)
        .into_diagnostic()
        .wrap_err("Opening file failed")?;
    let b: usize = bar(fail)?;
    Ok(b)
}

fn main() -> Result<()> {
    let value = foo("does_not_exist.txt", false)?;
    println!("{}", value);

    Ok(())
}
Error:   × Opening file failed
  ╰─▶ The system cannot find the file specified. (os error 2)

您选择哪一个当然是个人最喜欢的。

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