What’s the benefit of using a Result?

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

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)