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.
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"), } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With