Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the benefit of using a Result?

Tags:

rust

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.

like image 978
Doug Avatar asked Mar 05 '14 03:03

Doug


1 Answers

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"),     } } 
like image 161
Chris Morgan Avatar answered Sep 28 '22 03:09

Chris Morgan