Skip to content
Pitka edited this page Feb 4, 2021 · 7 revisions

Result

Represents either an Ok with underlying value T or Err with underlying value E. Like with Option, all behavior should act as close to possible as it does in Rust's implementation.

Instantiation

var ok = Result<int, string>.Ok(5);
var err = Result<int, string>.Err("Error!");

Checking the underlying value

Two functions are provided to see check if it is Ok or Err.

var ok = Result<string, int>.Ok("Hi!");
Console.WriteLine(o.IsOk()); // Prints true
Console.WriteLine(o.IsErr()); // Prints false

Unwrapping - Returning the underlying value

Unwrap : Result<T, E> -> T

Returns the underlying value if current is Ok, else throws an exception.

var okay = Result<int, string>.Ok(10);
int value = okay.Unwrap(); // Returns 10

var notOkay = Result<int, string>.Err("Oops!");
value = notOkay.Unwrap(); // Throws an exception!

UnwrapErr : Result<T, E> -> E

Returns the underlying value if current is Err, else throws an exception.

var okay = Result<int, string>.Ok(10);
string value = okay.UnwrapErr(); // Throws an exception!

var notOkay = Result<int, string>.Err("Oops!");
value = notOkay.UnwrapErr(); // Returns "Oops!"

Mapping - Performing transformations

Map : Result<T, E>(Func<T, U> f) -> Result<U, E>

Map() is a function for mapping a Result<T, E> to a Result<U, E> by invoking a passed function. If the current is Ok, the function is invoked with the underlying value, returning a new Ok of a different type. If it is Err, the result is still Err.

var res = Result<int, string>.Ok(10).Map(n => n.ToString()); // Returns Ok("10")
var none = Result<int, string>.Err("Oops!").Map(n => n.ToString()); // Returns Err("Oops!")

MapOr : Result<T, E>(U fallback, Func<T, U> okFunc) -> U

Provides a fallback value to return in case the current is Err, else returns the result of the function when invoked on the underlying Ok.

var res = Result<int, string>.Ok(10).MapOr("Default", n => n.ToString()); // Returns Ok("10")
var none = Result<int, string>.Err("Oops!").MapOr("Default", n => n.ToString()); // Returns Ok("Default")

MapOrElse : Result<T, E>(Func<U> fallbackFunc, Func<T, U> okFunc) -> U

Provides a fallback function to lazily evaluate in a closure if the current is Err, else returns the result of the function when invoked on the underlying Ok.

var res = Result<int, string>.Ok(10).MapOrElse(() => "Default", n => n.ToString()); // Returns Ok("10")
var none = Result<int, string>.Err("Oops!").MapOrElse(() => "Default", n => n.ToString()); // Returns Ok("Default")

MapErr : Result<T, E>(Func<E, F> f) -> Result<T, F>

Map() is a function for mapping a Result<T, E> to a Result<T, F> by invoking a passed function. If the current is Err, the function is invoked with the underlying value, returning a new Err of a different type. If it is Ok, the result is still Ok.

var res = Result<string, int>.Ok("Hi!").MapErr(n => n.ToString()); // Returns Ok("Hi!")
var none = Result<string, int>.Err(10).MapErr(n => n.ToString()); // Returns Err("10")

Combinators

AndThen : Result<T, E>(Func<Result<T, E>, Result<U, E>> f) -> Result<U, E>

AndThen() allows chaining. Each function in the chain returns a Result, calling the passed function on its underlying value if Ok, or returning Err if there isn't one. If any of the calls in the chain return Err, the end result is Err.

// Assume the following function exists
private Result<int, string> SquareIfOk(Result<int, string> res)
    => res.IsOk()
    ? res.Map(n => n * 2)
    : res;

public void SomeOtherScope()
{
    var okay = Result<int, string>.Ok(10);
    okay.AndThen(SquareIfOk); // Returns Ok(100)

    var notOkay = Result<int, string>.Err("Oops!");
    okay.AndThen(SquareIfOk); // Returns Err("Oops!")
}

Filtering

Or : Result<T, E>(Result<T, E> other) -> Result<T, E>

Returns the current Result if Ok, else returns the argument. Note that this does not guarantee the other is Ok.

var okay = Result<int, string>.Ok(10).Or(Result<int, string>.Ok(20));
// okay: Ok(10)
var notOkay = Result<int, string>.Err("Oops!").Or(Result<int, string>.Ok(20));
// notOkay : Ok(20)

Match - Actions instead of Funcs

This can be used for I/O operations or when a side effect is desired. This is made to mimic the match block in Rust, but obviously has its downsides as you cannot handle many different values like in Rust.

var res = Result<string, int>.Ok("Ok!");
o.Match(
    s => Console.WriteLine(s),
    e => someFile.WriteLine(e)
);
// Prints "Ok!" to the console

var res = Result<string, int>.Err(100);
o.Match(
    s => Console.WriteLine(s),
    e => someFile.WriteLine(e)
);
// Prints 100 to some file

Equity and Hashing

Results are safe to use in hashed collections. It is also safe to use the == operator on them.