handler returns.eve.types
eve.types — reusable sum-type storage primitives.
This module provides the binary tagged union Either and the unit type None: the value storage that eve.result.Result is built on, available on its own for new sum-typed APIs.
Either is a position-based sum: Either!(L, R) holds either an L (the left alternative) or an R (the right alternative); Either!(int,
int) is legal because dispatch is by position, not by type. Construction, copying and inspection are allocation-free and non-throwing when the relevant payload operations are.
Semantics:
- A default-constructed
EitherholdsL.initin the leftalternative:
Either.init == Either.left(L.init). - Payload types are unconstrained apart from distinct positions;
types equal after
Unqualnormalization remain legal (unlikeeve.result.Result, whose role selection needs distinct types). - Accessing the inactive alternative through Either.left or
Either.right is a programming error. It terminates the process with a diagnostic on
stderr— even in release builds — without allocating or throwing, exactly like the accessors ofeve.result. - Lifetime management is explicit and mirrors Phobos'
SumType: a Dunion manages no member lifetimes, so copies, destruction and assignment act on the active member only. For plain-value payloads no explicit lifetime member is generated and
Eitherstays POD.
Example:
import eve.types : Either, match;
alias Text = Either!(string, Exception);
Text decode(bool good) @safe {
if (good)
return Text.right(new Exception("fallback"));
return Text.left("malformed");
}
// Positional, exhaustive matching:
auto value = decode(false);
auto named = decode(true);
int code = value.match!(
(text) => text.length,
(error) => 1
);Design record: doc/EVE-TYPES.md in the repository documents the storage, lifetime and safety decisions behind this module.
Types 3
The unit type: a value that carries no information.
D has no built-in unit type that can be stored as a field (empty structs force memory-class returns through unions, and typeof(null) is pointer-sized), so None reserves one byte instead. It compares equal to every other None and hashes consistently. Use it as an alternative payload when an Either branch needs no data, or wherever a "no value" marker distinct from every data type is required.
private ubyte _reservedReserved storage. The DMD-frontend x86-64 ABI classifier treats a union whose member is an empty struct as memory class; one byte keeps containing unions register-classifiable. It never carries inf...A value of exactly one of two types, distinguished by position.
Either!(L, R) privately stores a tagged union of L and R and adds unambiguous construction, tag queries, fail-fast payload access, structural equality and positional matching on top. See the module documentation for the lifetime and misuse contracts.
Parameters
L | the left alternative's payload type. |
R | the right alternative's payload type. |
private Storage _storageprivate bool _isRightWhether the right alternative is active.private _leftMisuseMsgDiagnostic for left-payload access on a right alternative.private _rightMisuseMsgDiagnostic for right-payload access on a left alternative._LeftBox!S _readLeft(S)(return ref S storage) if (is(S == Storage) || is(S == const(Storage))
|| is(S == immutable(Storage)) || is(S == inout(Storage))) static ref pure nothrow @nogc @trustedRead the left alternative's storage without qualifier loss._RightBox!S _readRight(S)(return ref S storage) if (is(S == Storage) || is(S == const(Storage))
|| is(S == immutable(Storage)) || is(S == inout(Storage))) static ref pure nothrow @nogc @trustedRead the right alternative's storage without qualifier loss.inout(R) _rightPayload() ref inout return pure nothrow @nogcPackage-level payload read for dispatching callers that have already checked the tag: the misuse diagnostic of `right()` is skipped, keeping this path pure like the exhaustive `match` built on it.inout(L) _leftPayload() ref inout return pure nothrow @nogcPackage-level payload read for dispatching callers that have already checked the tag (see `rightPayload`).inout(L) _payloadFor(bool right)() if (!right) ref inout returnSelect a payload reference by compile-time alternative.Either left(T)(auto ref T value) if (is(T == L)) staticConstruct an `Either` holding the left alternative.Either right(T)(auto ref T value) if (is(T == R)) staticConstruct an `Either` holding the right alternative.inout(Storage) _copiedLeftInout()(ref inout(typeof(this)) other) static @trustedCopy the storage's left alternative, preserving qualifiers.immutable(Storage) _copiedLeftImmutable()(
ref immutable(typeof(this)) other) static @trustedDitto.inout(Storage) _copiedRightInout()(ref inout(typeof(this)) other) static @trustedCopy the storage's right alternative, preserving qualifiers.immutable(Storage) _copiedRightImmutable()(
ref immutable(typeof(this)) other) static @trustedDitto.typeof(this) _replaceLeft()(L value) refReplace the active alternative with `value` as the left one.typeof(this) _replaceRight()(R value) refReplace the active alternative with `value` as the right one.void _misuse(string msg)() static @nogc nothrow @trustedTerminates the process after writing a compile-time-constant diagnostic.StorableField wrapper that keeps union storage default-declarable for payloads that disable default construction: the explicit `T.init` initializer is the same static initializer the field would receive im..._LeftBox(S)Qualifier-preserving left-box type of a storage type: `Storable!L` under `S`'s qualifier (mutable, const, immutable or inout)._RightBox(S)Qualifier-preserving right-box type of a storage type.Success-like single-reserved-byte payload for layout asserts.
private ubyte _reservedFunctions 2
auto _dispatchExact(string context, T, handlers...)(ref T payload)Invoke the handler whose sole parameter exactly matches `T`, ignoring top-level qualifiers. This is the type-dispatch core shared by `eve.types.match` callers and `eve.result.match`: exact dispatch...auto _dispatchExactAt(string context, size_t index, T, handlers...)(ref T payload)Recursive implementation for `dispatchExact`.Variables 1
isPlainPayload = isCopyable!T && !hasElaborateCopyConstructor!T
&& !hasElaborateDestructor!T && !hasElaborateAssign!TInternal fast-path predicate: true when values of T can be stored, copied, assigned and destroyed without any explicit lifetime management.
Plain payloads keep Either POD (register-returnable on x86-64) and use the compiler-generated copy, assignment and destruction. This is an implementation detail of eve.types, not a public constraint: elaborate payloads are equally supported through the explicit lifetime members.
Parameters
T | payload type to inspect. |
Templates 2
Package dispatch core: invoke handler with the active payload by reference, under the either's qualifier.
One free callback mirrors the storage-dispatch shape eve.result.match is built on: the callback wraps the exact-type dispatch and captures only template aliases, so handler closures stay allocation-friendly.
Parameters
either | either whose active payload is handed over, under any qualifier. |
handler | callable accepting the active payload by reference. |
Returns
Exhaustive positional handling over both alternatives of an Either.
This is a free UFCS function so handlers may capture their enclosing scope. Selective imports must include match: import eve.types : Either, match;. Attributes depend on the handlers; capturing delegates can require GC allocation.
Handlers are matched by position: the first handler receives the left payload, the second the right payload, so Either!(int, int) works. A handler is accepted when it is callable with its position's payload type. Only lvalue eithers are accepted: store a temporary in a named variable before matching (same rule as eve.result.match).
Parameters
handlers | exactly two callables: onLeft then onRight. |
either | lvalue either to inspect by reference without copying. |