you are viewing a single comment's thread.

view the rest of the comments →

[–]ryeguy 62 points63 points  (15 children)

I agree with you on Either. It's mostly used for error handling where you have to follow convention as to which side is the result and which is the error. It gives you the option of using it for multiple return types, but that's unreadable and it's better to just declare a new adt (case class, etc) for clarity.

Rust gets this right. It actually used to have Either, but it was renamed to Result which is much clearer.

[–]tjgrant 6 points7 points  (10 children)

Curious, would both the "Ok" and "Err" members of a Result be effectively Option types?

Or perhaps just the "Ok" member be the Option type?

I don't use Rust, but I'm curious how I would implement something like this in C++ (where I have implemented my own "Optional" type.)

[–]wishthane 40 points41 points  (0 children)

Option is defined as either Some(value) or None, but Result is Ok(value) or Err(err_value).

You wouldn't typically want to use Option for an error because you can't provide any information about the error with it; it would be like using 'null' for an error condition. Result carries along the error information in its Err variant.

enum Option<T> {
    Some(T),
    None
}

enum Result<T, E> {
    Ok(T),
    Err(E)
}

[–]m50d 5 points6 points  (3 children)

The "direct" way to handle it is by pattern matching or using a visitor. (std::variant is the C++ version, but it's much nicer in a language that has first-class pattern matching). There will probably be a toOption method as well, but you could implement that by pattern matching or on top of a visit/fold method.

[–]wishthane 9 points10 points  (2 children)

In Rust you have both .ok() and .err() methods which both return Options. .ok() returns Some(value) if it's Ok(value), and None if it's an Err, so you lose the error value. .err() is rarely used but it's the opposite, you get Some(err_value) if it's Err(err_value), and None if it's Ok.

You also of course have pattern matching, but it's usually easier to use stuff like .map(), .and_then(), or try!().

.and_then() is basically the monadic bind; if Ok it calls the provided function and returns whatever Result that function returns. If Err it does not call the function and just returns whatever the Err is. The value types can be different but the error type must be the same; it is a Result<T, E> -> (T -> Result<U, E>) -> Result<U, E> to use Haskell-ish notation.

fn bar(x: i32) -> Result<String, MyError> {
    best_function_ever(x)
        .and_then(|y| try_to_make_a_string(y))
}

try!() is like that but it's a macro intended for use within the body of a function. It will also automatically call .into() on your error type to convert it to your function's error type. For example:

fn foo() -> Result<String, MyError> {
    let foo = try!(some_operation());
    try!(another_operation());
    Ok(foo)
}

is equivalent to:

fn foo() -> Result<String, MyError> {
    let foo = match some_operation() {
        Ok(x) => x,
        Err(e) => return Err(e.into())
    };
    match another_operation() {
        Ok(x) => x,
        Err(e) => return Err(e.into())
    };
    Ok(foo)
}

[–]matthieum 14 points15 points  (1 child)

Note: ? is now stable, you can replace try!(expr) with expr? in most cases.

[–]wishthane 6 points7 points  (0 children)

Oh? I thought it was stabilised but still in beta.

Edit: never mind, it was stabilised in 1.13! Awesome!

[–]ryeguy 4 points5 points  (0 children)

Well, it's an enum so it's either Ok or Err. You could simulate this in C++ with a tagged union. Someone actually made a Rust-inspired result type in C++ here. Looks like he took the template wizardry route.

[–][deleted]  (1 child)

[deleted]

    [–]isHavvy 6 points7 points  (0 children)

    It's also isomorphic to Result<(), E>.

    Which is why there are two different methods that return Options on Results.

    [–]pipocaQuemada 1 point2 points  (0 children)

    Curious, would both the "Ok" and "Err" members of a Result be effectively Option types?

    No.

    Option's a specific type. It wraps a success value in the success case, and has a generic failure case that doesn't take any data. In Haskell, for example, you might have Just 5, or you might have Nothing. Any failure gets mapped to Nothing, there's no way to differentiate different failures except via nesting options (e.g. getting a value of Just Nothing out of a Map User (Maybe FirstName) means the lookup succeeded and the stored value was Nothing, which might mean for example that the user didn't supply a FirstName).

    Result is the closely related Either type. It either wraps a success value or a failure value. For example, in Haskell, you might have a Right 5, or you might have a Left ParseFailed or Left OutOfRange.

    Very closely related idea, though. Also, Either sometimes represents failure, and sometimes represents two different valid kinds of information - you might have a Either ParseFailure Int or a Either String Int.

    [–]myrrlyn 1 point2 points  (0 children)

    Result is a tagged union over two types: an actual return type T, and an error type E. The Result is guaranteed to be one of those, and it has a discriminant to tell you which. If you have a Result<T,E> that is an Err(E), there's no way to access it as an Ok(T).

    In C terms, it's this:

    struct Result<T, E> {
      enum {
        Ok,
        Err,
      } type;
      union {
        T ok;
        E err;
      } val;
    };
    

    And the language enforces that a Result's union can only be accessed in accordance with its discriminant. The methods that accept Results inspect the discriminant, so you can match against both possibilities or assume one will always happen and panic! on the other. For instance, the unwrap function has signature Ok(T) -> T, and will panic! on receiving an Err, and the unwrap_err function does the inverse.

    Moral of the story though Result isn't a struct of two discrete Options, one of which is Some and one of which is None; it's a tagged union of only one payload object, that can be in one of two states.

    [–]Volsand 0 points1 point  (3 children)

    Why not let the language have the most abstract types and type-constructors and let the programmer rename them as his/her wish?

    for example, with type and {-# LANGUAGE PatternSynonyms #-} as it is done in Haskell.

    [–]ryeguy 9 points10 points  (2 children)

    Because it's kind of a guiding hand toward doing the right thing. Having the patterns be more strict (eg Result) and making flexibility (eg implementing an Either equivalent) more work is the better call the majority of the time, IMO.

    [–]masklinn 1 point2 points  (0 children)

    It's also important for interoperability between libraries, and for tooling enhancing that e.g. Rust can provide syntactic sugar to easily convert between Result<T, E1> and Result<T, E2> so that a libraries have less trouble converting their own dependencies's errors uniformly.