| 0.1.0 |
|
|---|
#73 在 #result
22KB
329 代码行
Errable
Errable 是一个具有逆 Try 语义的 Option。
这意味着在 ? 运算符作用于 Errable<E> 时,如果包含错误 E,则会提前退出;如果值是 Success,则作为无操作执行。
这与 Option 相比,在 None 值上使用 ? 会提前退出。
Errable 填补了 Result 和 Option 类型留下的空白
| 可能的成功 | 可能失败 |
|---|---|
结果<T |
,E> |
选项<T> |
Errable<E> |
示例
以下代码演示了如何使用 Errable 编写简洁的验证代码,在失败时提前退出。
use errable::Errable::{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) -> Errable<&'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() -> Errable<&'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(), Errable::Fail("number is zero"));
动机
Errable 填补了 Option 和 Result 留下的空白,并清楚地传达了函数的意图和潜在结果。
返回 Errable 的函数只有两种潜在结果,它可以失败并带有错误 E,或者它可以成功。
为什么不使用 Result 呢?
因为 Result 暗示了输出。以 std::fs::rename 为例
如果我说 rename 的返回类型是 Result<T, E>,你猜 T 和 E 会是什么?
你可能会合理地假设 E 是 std::io::Error,但 T 呢?它可以合理地返回任何数量的事物
- 重命名文件目的地的规范路径。
- 移动文件的大小。
- 被重命名文件替换的文件的大小(如果有)。
- 或者甚至可能是被覆盖文件的句柄。
当然,这些都不正确,因为T的值是单位值()。函数rename永远不会产生任何输出,只能信号错误。那么为什么不明确地通知用户呢?
我认为使用一个可以表示失败潜力的类型,但在成功时没有输出,可以更清楚地表达使用此函数时的意图和潜在结果。
为什么不使用Option呢?
潜在的失败可以使用Option<E>来表示,但如上所述,Option的
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切换到Errable非常简单,以下是一个前后示例来展示这一点。
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")
}
}
使用Errable
fn validate_number(x: u32) -> Errable<&'static str> {
match x {
0 ..= 9 => Fail("number is too small"),
10..=30 => Success,
31.. => Fail("number is too large")
}
}
兼容性
Errable包含将到和从Result和Option进行映射的实用函数,以及FromResidual实现,当与?运算符一起使用时,可以自动执行这些转换。
fn fails_if_true(should_fail: bool) -> Errable<&'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)
}