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