expected

This module is implementing the Expected idiom.

See the Andrei Alexandrescu’s talk (Systematic Error Handling in C++ and its slides.

Or more recent "Expect the Expected" by Andrei Alexandrescu for further background.

It is also inspired by C++'s proposed std::expected and Rust's Result.

Similar work is expectations by Paul Backus.

Main differences with that are:

  • lightweight, no other external dependencies
  • allows to use same types for T and E
  • allows to define Expected without value (void for T)
  • provides facility to change the Expected behavior by custom Hook implementation using the Design by introspection.
  • can enforce result check (with a cost)

Default type for error is string, i.e. Expected!int is the same as Expected!(int, string)

Expected has customizable behavior with the help of a third type parameter, Hook. Depending on what methods Hook defines, core operations on the Expected may be verified or completely redefined. If Hook defines no method at all and carries no state, there is no change in default behavior. This module provides a few predefined hooks (below) that add useful behavior to Expected:

Abortfails every incorrect operation with a call to assert(0). It is the default third parameter, i.e. Expected!short is the same as Expected!(short, string, Abort).
Throwfails every incorrect operation by throwing an exception.

The hook's members are looked up statically in a Design by Introspection manner and are all optional. The table below illustrates the members that a hook type may define and their influence over the behavior of the Checked type using it. In the table, hook is an alias for Hook if the type Hook does not introduce any state, or an object of type Hook otherwise.

Hook memberSemantics in Expected!(T, E, Hook)
enableDefaultConstructorIf defined, Expected would have enabled or disabled default constructor based on it's bool value. Default constructor is disabled by default. opAssign for value and error types is generated if default constructor is enabled.
enableCopyConstructorIf defined, Expected would have enabled or disabled copy constructor based on it's bool value. It is enabled by default. When disabled, it enables automatic check if the result was checked either for value or error. When not checked it calls hook.onUnchecked if provided.
WARNING: As currently it's not possible to change internal state of const or immutable object, automatic checking would't work on these. Hopefully with __mutable proposal..
onAccessEmptyValueIf value is accessed on unitialized Expected or Expected with error value, hook.onAccessEmptyValue!E(err) is called. If hook doesn't implement the handler, T.init is returned.
onAccessEmptyErrorIf error is accessed on unitialized Expected or Expected with value, hook.onAccessEmptyError() is called. If hook doesn't implement the handler, E.init is returned.
onUncheckedIf the result of Expected isn't checked, hook.onUnchecked() is called to handle the error. If hook doesn't implement the handler, assert is thrown.
Note that hook.enableCopyConstructor must be false for checks to work.

Members

Classes

Unexpected
class Unexpected(T)

An exception that represents an error value.

Functions

andThen
EX andThen(EX exp, EX value)
EX andThen(EX exp)

Returns the error contained within the Expected _and then_ another value if there's no error. This function can be used for control flow based on Expected values.

expected
Expected!(T, E, Hook) expected(T value)
Expected!(void, E, Hook) expected()

Creates an Expected object from an expected value, with type inference.

orElse
U orElse(EX exp, U value)
auto ref orElse(EX exp)

Returns the value contained within the Expected _or else_ another value if there's an error. This function can be used for control flow based on Expected values.

unexpected
Expected!(T, E, Hook) unexpected(E err)

Creates an Expected object from an error value, with type inference.

Structs

Abort
struct Abort

Default hook implementation for Expected

Expected
struct Expected(T, E = string, Hook = Abort)

Expected!(T, E) is a type that represents either success or failure.

Throw
struct Throw

Hook implementation that throws exceptions instead of default assert behavior.

Templates

expected
template expected(alias fun, Hook = Abort)

Constructs Expected from the result of the provided function.

hasOnAccessEmptyError
template hasOnAccessEmptyError(Hook)

Template to determine if hook provides function called on empty error.

hasOnAccessEmptyValue
template hasOnAccessEmptyValue(Hook, E)

Template to determine if hook provides function called on empty value.

hasOnUnchecked
template hasOnUnchecked(Hook)

Template to determine if hook provides custom handler for case when the Expected result is not checked.

isCopyConstructorEnabled
template isCopyConstructorEnabled(Hook)

Template to determine if hook enables or disables copy constructor.

isDefaultConstructorEnabled
template isDefaultConstructorEnabled(Hook)

Template to determine if provided Hook enables default constructor for Expected

map
template map(alias op, Hook = Abort)

Applies a function to the expected value in an Expected object.

mapError
template mapError(alias op, Hook = Abort)

Applies a function to the expected error in an Expected object.

mapOrElse
template mapOrElse(alias valueOp, alias errorOp)

Maps a Expected<T, E> to U by applying a function to a contained value, or a fallback function to a contained error value.

Examples

Basic usage

auto foo(int i) {
	if (i == 0) return unexpected!int("oops");
	return expected(42 / i);
}

auto bar(int i) {
	if (i == 0) throw new Exception("err");
	return i-1;
}

// basic checks
assert(foo(2));
assert(foo(2).hasValue);
assert(!foo(2).hasError);
assert(foo(2).value == 21);

assert(!foo(0));
assert(!foo(0).hasValue);
assert(foo(0).hasError);
assert(foo(0).error == "oops");

// void result
assert(Expected!(void)()); // no error -> success
assert(!Expected!(void)().hasError);
// assert(unexpected("foo").value); // doesn't have hasValue and value properties

// expected from throwing function
assert(expected!bar(1) == 0);
assert(expected!bar(0).error.msg == "err");

// orElse
assert(foo(2).orElse!(() => 0) == 21);
assert(foo(0).orElse(100) == 100);

// andThen
assert(foo(2).andThen(foo(6)) == 7);
assert(foo(0).andThen(foo(6)).error == "oops");

// map
assert(foo(2).map!(a => a*2).map!(a => a - 2) == 40);
assert(foo(0).map!(a => a*2).map!(a => a - 2).error == "oops");

// mapError
assert(foo(0).mapError!(e => "OOPS").error == "OOPS");
assert(foo(2).mapError!(e => "OOPS") == 21);

// mapOrElse
assert(foo(2).mapOrElse!(v => v*2, e => 0) == 42);
assert(foo(0).mapOrElse!(v => v*2, e => 0) == 0);

Meta

License

BSL-1.0

Authors

Tomáš Chaloupka