Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I don't understand why Result exists in Rust. I can see how Option can be useful, but using Result just seems to complicate code unnecessarily.

Consider the following example:

#[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"),
    }
}

The only thing I can see is that it minorly increases the convenience of function writers, as you can simply call Ok() and Err() to return results.

I've seen some people saying its so you can use conditions, but that's not true at all; you can use conditions perfectly well using tuples. (Note — "conditions" were a feature of Rust that were removed before 1.0)

I've also seen some people saying that Result is more performant that returning a tuple, but Result is a tuple, so I don't see how this can be the case.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
581 views
Welcome To Ask or Share your Answers For Others

1 Answer

Let's consider the definition of 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)
}

Distilled to the bits that matter, it's enum Result<T, E> { Ok(T), Err(E) }.

That is not a tuple (T, E); rather, it is either a T (OK) or an E (error).

If you use a tuple (T, E), you must define both the T and the E. For your returns_tuple, that meant defining 0 as a magic value and adding a new variant to your MyErr enumeration, None. None is not an error; it is semantically unsound to model it thus. It also then propagates to other places because of the requirement of exhaustive matching.

When you are dealing with more complex types, defining a dummy value may be less feasible or more expensive. As a generalisation, having dummy values is not a good plan. It's far too likely that somewhere down the track you will actually try to use them.

Rust has a good type system which allows you to avoid these sorts of problems.

It looks to me like you've missed the power of Rust's matching; in fact, the only way to get a value out of an enum is pattern matching; in consequence, things like Result.ok(), Result.err() and Option.unwrap() are implemented in terms of pattern matching.

Now let's write your example in a way that shows Rust in a better light.

#[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"),
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...