#fallible #try #result #error-handling

nightly fallible-option

Fallible是一种具有反转Try语义的Option

4个版本

0.1.3 2023年1月12日
0.1.2 2023年1月12日
0.1.1 2023年1月12日
0.1.0 2023年1月12日

#2026Rust模式

每月下载量 31

MIT 许可证

27KB
384

Fallible 最新版本 文档

Fallible 是一个具有反转 Try-语义的 Option

这意味着在对一个 Fallible<E> 使用 ? 运算符时,如果包含错误 E 将会提前退出,或者如果值是 Success 则不进行任何操作。

这与在 Option 中使用 ? 运算符在 None 值上将会提前退出形成对比。

Fallible 填补了 ResultOption 类型留下的空白

可能的成功 可能失败
结果<T ,E>
Option<T> Fallible<E>

示例

此代码演示了如何使用 Fallible 来编写简洁的验证代码,在失败时提前退出。

use fallible_option::Fallible::{self, Fail, Success};

// Validates the input number `n`, returning a `Fail`
// if the input number is zero, or `Success` otherwise.
fn fails_if_number_is_zero(n: u32) -> Fallible<&'static str> {
    if n == 0 {
        Fail("number is zero")
    } else {
        Success
    }
};

// Check many numbers, returning early if a tested
// number is equal to zero.
fn check_many_numbers() -> Fallible<&'static str> {
    fails_if_number_is_zero(1)?;
    fails_if_number_is_zero(3)?;
    fails_if_number_is_zero(0)?; // <--- Will cause early exit

    // Following lines are never reached
    fails_if_number_is_zero(10)?;
    
    Success
}

assert_eq!(check_many_numbers(), Fallible::Fail("number is zero"));

动机

Fallible 填补了 OptionResult 留下的空白,并清楚地传达了函数的意图和潜在结果。

返回 Fallible 的函数只有两种可能的结果,它可能因为错误 E 而失败,或者成功。

为什么不是 Result

因为 Result 暗示了输出。以 std::fs::rename 为例

如果我告诉你 rename 函数的返回类型是 Result<T, E>,你会猜测 TE 分别是什么吗?

你可能正确地假设 Estd::io::Error,但 T 呢?它可能返回任何数量的事物

  • 重命名文件目的地的规范路径。
  • 移动文件的大小。
  • 被重命名文件替换的文件(如果有)的大小。
  • 或者甚至可能是被覆盖文件的句柄。

当然,这些都不是真的,因为 renameT 值是单元值 ()rename 永远不会产生任何输出,它只能报告错误。那么为什么不让用户清楚地知道这一点呢?

我认为使用一个表示潜在失败但成功时无输出的类型,可以更清楚地表达使用此函数时的意图和潜在结果。

为什么不使用 Option 呢?

潜在失败可以用 Option<E> 来表达,但如上所述,OptionTry 语义使得使用起来不太方便。

type Error = &'static str;

fn fails_if_number_is_zero(n: u32) -> Option<Error> {
    if n == 0 {
        Some("number is zero")
    } else {
        None
    }
};

fn check_many_numbers() -> Option<Error> {
    // We have to explicitly check, since using `?` here would result in an early exit,
    // if the call returned None, which is the opposite of what we intend.
    if let Some(err) = fails_if_number_is_zero(1) {
        return Some(err)
    }

    // .. Repeating the above three lines for each check is tedious compared to
    // just using the `?` operator, as in the example.

    None
}

Result 转换

从使用 Result 切换到 Fallible 非常简单,以下是一个示例

fn validate_number(x: u32) -> Result<(), &'static str> {
    match x {
        0 ..= 9 => Err("number is too small"),
        10..=30 => Ok(()),
        31..    => Err("number is too large")
    }
}

使用 Fallible

fn validate_number(x: u32) -> Fallible<&'static str> {
    match x {
        0 ..= 9 => Fail("number is too small"),
        10..=30 => Success,
        31..    => Fail("number is too large")
    }
}

兼容性

Fallible 包含将类型映射到和从 ResultOption 以及 FromResidual 的实用函数,当与 ? 操作符一起使用时,可以自动执行这些转换。

fn fails_if_true(should_fail: bool) -> Fallible<&'static str> {
    if should_fail {
        Fail("Darn it!")
    } else {
        Success
    }
}

fn try_producing_value() -> Result<u32, &'static str> {
    fails_if_true(false)?;
    fails_if_true(true)?;

    Ok(10)
}

无运行时依赖