使用Result有什么好处?

38

我不明白为什么Rust中存在Result。我能理解Option的用处,但是使用Result似乎只会使代码变得更加复杂。

考虑以下示例:

#[derive(PartialEq, Eq, Debug)]
enum MyErr {
    None,
    FailOne,
}

fn returns_tuple() -> (u8, MyErr) {
    // (1, None) // <-- Success path
    (0, MyErr::FailOne)
}

fn returns_result() -> Result<u8, MyErr> {
    // Ok(1) // <-- Success path
    Err(MyErr::FailOne)
}

#[test]
fn test_check_return_values() {
    let x = returns_result();
    if x.is_ok() {
        println!("result: Is OK: {}", x.unwrap()); // <-- Must use unwrap
    } else {
        match x.err().unwrap() { // <-- Again, unwrapping
            MyErr::None => {}, // Required for match
            MyErr::FailOne => println!("result: Failed One"),
        }
    }
}

#[test]
fn test_check_return_values_2() {
    let (y, err) = returns_tuple();
    match err {
        MyErr::None => println!("tuple: Is OK: {}", y),
        MyErr::FailOne => println!("tuple: Failed one"),
    }
}

我能看到的唯一好处是它可以稍微提高函数编写者的便利性,因为你可以简单地调用Ok()Err()来返回结果。

有些人说这样做是为了使用条件语句,但这完全不是真的;你可以很好地使用元组来使用条件语句。(注意——“条件”是Rust的一个在1.0之前就被删除的特性)

我也看到一些人说Result比返回元组更有效率,但Result实际上是一个元组,所以我不明白这怎么能成立。

2个回答

48

让我们来考虑 Result 的定义

/// `Result` is a type that represents either success (`Ok`) or failure
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use]
pub enum Result<T, E> {
    /// Contains the success value
    Ok(T),

    /// Contains the error value
    Err(E)
}

精简后的内容是:enum Result<T, E> { Ok(T), Err(E) }

这不是一个元组 (T, E),而是要么是 T(表示成功),要么是 E(表示错误)。

如果您使用元组 (T, E),则必须定义 TE。对于您的 returns_tuple,这意味着将0定义为魔法值,并向您的 MyErr 枚举添加一个新变量 NoneNone 不是错误;用这种方式对其进行建模在语义上是不合理的。由于需要穷尽匹配的要求,它还会传播到其他地方。

当您处理更复杂的类型时,定义虚拟值可能不太可行或更加昂贵。一般而言,具有虚拟值并不是一个好计划。因为很有可能在以后的某个时候您会尝试实际使用它们。

Rust 有一个良好的类型系统,可以帮助您避免这些问题。

在我看来,您错过了 Rust 匹配的强大之处;事实上,从枚举中获取值的唯一方法是模式匹配;因此,Result.ok()Result.err()Option.unwrap() 等操作都是基于模式匹配实现的。

现在让我们用更好的方式编写您的示例,展示 Rust 的优势。

#[derive(PartialEq, Eq, Debug)]
enum MyErr {
    // Now we don't need that phoney None variant.
    FailOne,
}

fn returns_result() -> Result<u8, MyErr> {
    Err(MyErr::FailOne)
}

#[test]
fn test_check_return_values() {
    match returns_result() {
        Ok(num) => println!("result: Is OK: {}", num),
        Err(MyErr::FailOne) => println!("result: Failed One"),
    }
}

14
我来晚了,但我认为你忽略了一个非常重要的点;Result 通过命名其可能的值来作为文档。这可能看起来很小,但实际上当你写很多代码时,你需要查看返回 (a, b) 的函数的文档才能确定发生了什么;谁知道它们在成功时返回1或0,或者根本不是错误值?如果我调用期望 Foo 的函数并得到 Result<Foo, _>,我可以确定它正在处理潜在错误,并且我毫不怀疑哪些返回值是成功的值(那些包装在 Ok 中的值)。 - sleeparrow

1

Option 用于表示可为空的值,可能有值也可能没有,它基本上包装了一个可为空的值,并强制您每次想要访问它时都要检查可能的空值情况。

Result 用于表示易出错操作的结果,例如IO操作或网络请求,由于存在失败的可能性,类型系统强制您每次想要访问结果数据时都要检查可能的错误情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接