sumtype-variants.dhover×118all
#!/usr/bin/env dub
/+ dub.sdl:
    name "property_tree_sumtype_variants"
    targetPath "build"
    dflags "-preview=in" "-preview=dip1000"
    buildType "checked" {
        buildOptions "optimize" "inline" "debugInfo"
    }
+/
/**
 * The variant-switch problem, in D terms.
 *
 * Backs [../comparison.md](../comparison.md) § _polymorphic and sum-typed
 * values_. The sharpest evidence in the survey is
 * [`bevy-inspector-egui`](../bevy-inspector-egui.md): switching a Rust enum to
 * another variant means **constructing** that variant, so the library asks the
 * type registry whether every field of the candidate variant has a
 * `ReflectDefault` and _greys out the entries it cannot build_
 * (`variant_constructable`, `crates/bevy-inspector-egui/src/reflect_inspector/mod.rs:1885`).
 *
 * D moves that question twice, and this program measures both moves:
 *
 *   1. **Constructability is nearly free.** Every type has `.init`, so a picker
 *      almost never has to grey an entry out. The exception is an author's
 *      explicit `@disable this()`, which removes `T()` while leaving `T.init`
 *      readable — so "can I display it?" and "can I switch to it?" stay two
 *      questions, just with a much rarer gap between them.
 *   2. **The safety verdict, not the construction, is what bites.** Phobos'
 *      `SumType.opAssign` is `@system` whenever _some other_ member type has
 *      indirections, because overwriting the payload can invalidate a reference
 *      into it (`std/sumtype.d`, `unsafeToOverwrite`). A variant picker over an
 *      arbitrary `SumType` therefore cannot be `@safe` — the switch needs a
 *      `@trusted` seam whose precondition is that no one holds a pointer into
 *      the old payload, which is exactly the invariant a retained node model
 *      full of pointers-to-fields would break.
 *
 * The table this prints is the per-variant answer to all three questions.
 *
 * Run: `dub run --single sumtype-variants.d`
 */
module 
(module) property_tree_sumtype_variants

The variant-switch problem, in D terms.

Backs ../comparison.md § polymorphic and sum-typed values_. The sharpest evidence in the survey is bevy-inspector-egui: switching a Rust enum to another variant means constructing that variant, so the library asks the type registry whether every field of the candidate variant has a ReflectDefault and greys out the entries it cannot build_ (variant_constructable, crates/bevy-inspector-egui/src/reflect_inspector/mod.rs:1885).

D moves that question twice, and this program measures both moves:

  1. Constructability is nearly free. Every type has .init, so a picker almost never has to grey an entry out. The exception is an author's explicit @disable this(), which removes T() while leaving T.init readable — so "can I display it?" and "can I switch to it?" stay two questions, just with a much rarer gap between them.

  2. The safety verdict, not the construction, is what bites. Phobos' SumType.opAssign is @system whenever some other_ member type has indirections, because overwriting the payload can invalidate a reference into it (std/sumtype.d, unsafeToOverwrite). A variant picker over an arbitrary SumType therefore cannot be @safe — the switch needs a @trusted seam whose precondition is that no one holds a pointer into the old payload, which is exactly the invariant a retained node model full of pointers-to-fields would break.

The table this prints is the per-variant answer to all three questions.

Run

dub run --single sumtype-variants.d

property_tree_sumtype_variants
;
import
(package) std
std
.
(module) std.stdio
Category Symbols
File handles _popen File isFileHandle openNetwork stderr stdin stdout
Reading chunks lines readf readfln readln
Writing toFile write writef writefln writeln
Misc KeepTerminator LockType StdioException

Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio is publically imported when importing std.stdio.

There are three layers of I/O:

  1. The lowest layer is the operating system layer. The two main schemes are Windows and Posix.

  2. C's stdio.h which unifies the two operating system schemes.

  3. std.stdio, this module, unifies the various stdio.h implementations into a high level package for D programs.

Source

std/stdio.d

@copyrightCopyright The D Language Foundation 2007-.@licenseBoost License 1.0.@authorsWalter Bright, Andrei Alexandrescu, Alex Rønne Petersen
stdio
:
(alias template) property_tree_sumtype_variants.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))

Equivalent to writef(fmt, args, '\n').

writefln
,
(alias template) property_tree_sumtype_variants.writeln = std.stdio.writeln(T...)(T args)

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
;
import
(package) std
std
.
(module) std.sumtype

SumType is a generic discriminated union implementation that uses design-by-introspection to generate safe and efficient code. Its features include:

  • Pattern matching.

  • Support for self-referential types.

  • Full attribute correctness (pure, @safe, @nogc, and nothrow are inferred whenever possible).

  • A type-safe and memory-safe API compatible with DIP 1000 (scope).

  • No dependency on runtime type information (TypeInfo).

  • Compatibility with BetterC.

List of examples

Source

std/sumtype.d

Examples

Basic usage

import std.math.operations : isClose;

struct Fahrenheit { double value; }
struct Celsius { double value; }
struct Kelvin { double value; }

alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);

// Construct from any of the member types.
Temperature t1 = Fahrenheit(98.6);
Temperature t2 = Celsius(100);
Temperature t3 = Kelvin(273);

// Use pattern matching to access the value.
Fahrenheit toFahrenheit(Temperature t)
{
    return Fahrenheit(
        t.match!(
            (Fahrenheit f) => f.value,
            (Celsius c) => c.value * 9.0/5 + 32,
            (Kelvin k) => k.value * 9.0/5 - 459.4
        )
    );
}

assert(toFahrenheit(t1).value.isClose(98.6));
assert(toFahrenheit(t2).value.isClose(212));
assert(toFahrenheit(t3).value.isClose(32));

// Use ref to modify the value in place.
void freeze(ref Temperature t)
{
    t.match!(
        (ref Fahrenheit f) => f.value = 32,
        (ref Celsius c) => c.value = 0,
        (ref Kelvin k) => k.value = 273
    );
}

freeze(t1);
assert(toFahrenheit(t1).value.isClose(32));

// Use a catch-all handler to give a default result.
bool isFahrenheit(Temperature t)
{
    return t.match!(
        (Fahrenheit f) => true,
        _ => false
    );
}

assert(isFahrenheit(t1));
assert(!isFahrenheit(t2));
assert(!isFahrenheit(t3));

Matching with an overload set

Instead of writing match handlers inline as lambdas, you can write them as overloads of a function. An alias can be used to create an additional overload for the SumType itself.

For example, with this overload set:

string handle(int n) { return "got an int"; }
string handle(string s) { return "got a string"; }
string handle(double d) { return "got a double"; }
alias handle = match!handle;

Usage would look like this:

alias ExampleSumType = SumType!(int, string, double);

ExampleSumType a = 123;
ExampleSumType b = "hello";
ExampleSumType c = 3.14;

assert(a.handle == "got an int");
assert(b.handle == "got a string");
assert(c.handle == "got a double");

Recursive SumTypes

This example makes use of the special placeholder type This to define a recursive data type: an abstract syntax tree for representing simple arithmetic expressions.

import std.functional : partial;
import std.traits : EnumMembers;
import std.typecons : Tuple;

enum Op : string
{
    Plus  = "+",
    Minus = "-",
    Times = "*",
    Div   = "/"
}

// An expression is either
//  - a number,
//  - a variable, or
//  - a binary operation combining two sub-expressions.
alias Expr = SumType!(
    double,
    string,
    Tuple!(Op, "op", This*, "lhs", This*, "rhs")
);

// Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"),
// the Tuple type above with Expr substituted for This.
alias BinOp = Expr.Types[2];

// Factory function for number expressions
Expr* num(double value)
{
    return new Expr(value);
}

// Factory function for variable expressions
Expr* var(string name)
{
    return new Expr(name);
}

// Factory function for binary operation expressions
Expr* binOp(Op op, Expr* lhs, Expr* rhs)
{
    return new Expr(BinOp(op, lhs, rhs));
}

// Convenience wrappers for creating BinOp expressions
alias sum  = partial!(binOp, Op.Plus);
alias diff = partial!(binOp, Op.Minus);
alias prod = partial!(binOp, Op.Times);
alias quot = partial!(binOp, Op.Div);

// Evaluate expr, looking up variables in env
double eval(Expr expr, double[string] env)
{
    return expr.match!(
        (double num) => num,
        (string var) => env[var],
        (BinOp bop)
        {
            double lhs = eval(*bop.lhs, env);
            double rhs = eval(*bop.rhs, env);
            final switch (bop.op)
            {
                static foreach (op; EnumMembers!Op)
                {
                    case op:
                        return mixin("lhs" ~ op ~ "rhs");
                }
            }
        }
    );
}

// Return a "pretty-printed" representation of expr
string pprint(Expr expr)
{
    import std.format : format;

    return expr.match!(
        (double num) => "%g".format(num),
        (string var) => var,
        (BinOp bop) => "(%s %s %s)".format(
            pprint(*bop.lhs),
            cast(string) bop.op,
            pprint(*bop.rhs)
        )
    );
}

Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
double[string] myEnv = ["a":3, "b":4, "c":7];

assert(eval(*myExpr, myEnv) == 11);
assert(pprint(*myExpr) == "(a + (2 * b))");
@licenseBoost License 1.0@authorsPaul Backus
sumtype
:
(alias template) property_tree_sumtype_variants.match = std.sumtype.match(handlers...)

Calls a type-appropriate function with the value held in a SumType.

For each possible type the SumType can hold, the given handlers are checked, in order, to see whether they accept a single argument of that type. The first one that does is chosen as the match for that type. (Note that the first match may not always be the most exact match. See "Avoiding unintentional matches" for one common pitfall.)

Every type must have a matching handler, and every handler must match at least one type. This is enforced at compile time.

Handlers may be functions, delegates, or objects with opCall overloads. If a function with more than one overload is given as a handler, all of the overloads are considered as potential matches.

Templated handlers are also accepted, and will match any type for which they can be implicitly instantiated. (Remember that a function literal without an explicit argument type is considered a template.)

If multiple SumTypes are passed to match, their values are passed to the handlers as separate arguments, and matching is done for each possible combination of value types. See "Multiple dispatch" for an example.

@returnsThe value returned from the handler that matches the currently-held type.@seevisit
match
,
(alias struct) property_tree_sumtype_variants.SumType = std.sumtype.SumType(Types...) if (is(NoDuplicates!Types == Types) && (Types.length > 0))

A tagged union that can hold a single value from any of a specified set of types.

The value in a SumType can be operated on using pattern matching.

To avoid ambiguity, duplicate types are not allowed (but see the "basic usage" example for a workaround).

The special type This can be used as a placeholder to create self-referential types, just like with Algebraic. See the "Recursive SumTypes" example for usage.

A SumType is initialized by default to hold the .init value of its first member type, just like a regular union. The version identifier SumTypeNoDefaultCtor can be used to disable this behavior.

@seeAlgebraic
SumType
;
import
(package) std
std
.
(module) std.traits

Templates which extract information about types and symbols at compile time.

Category Templates
Symbol Name traits fullyQualifiedName mangledName moduleName packageName
Function traits isFunction arity functionAttributes hasFunctionAttributes functionLinkage FunctionTypeOf isSafe isUnsafe isFinal ParameterDefaults ParameterIdentifierTuple ParameterStorageClassTuple Parameters ReturnType SetFunctionAttributes variadicFunctionStyle
Aggregate Type traits BaseClassesTuple BaseTypeTuple classInstanceAlignment EnumMembers FieldNameTuple Fields hasAliasing hasElaborateAssign hasElaborateCopyConstructor hasElaborateDestructor hasElaborateMove hasIndirections hasMember hasStaticMember hasNested hasUnsharedAliasing InterfacesTuple isInnerClass isNested MemberFunctionsTuple RepresentationTypeTuple TemplateArgsOf TemplateOf TransitiveBaseTypeTuple
Type Conversion CommonType AllImplicitConversionTargets ImplicitConversionTargets CopyTypeQualifiers CopyConstness isAssignable isCovariantWith isImplicitlyConvertible isQualifierConvertible
Type Constructors InoutOf ConstOf SharedOf SharedInoutOf SharedConstOf SharedConstInoutOf ImmutableOf QualifierOf
Categories of types allSameType ifTestable isType isAggregateType isArray isAssociativeArray isAutodecodableString isBasicType isBoolean isBuiltinType isCopyable isDynamicArray isEqualityComparable isFloatingPoint isIntegral isNarrowString isConvertibleToString isNumeric isOrderingComparable isPointer isScalarType isSigned isSIMDVector isSomeChar isSomeString isStaticArray isUnsigned
Type behaviours isAbstractClass isAbstractFunction isCallable isDelegate isExpressions isFinalClass isFinalFunction isFunctionPointer isInstanceOf isIterable isMutable isSomeFunction isTypeTuple
General Types ForeachType KeyType Largest mostNegative OriginalType PointerTarget Signed Unconst Unshared Unqual Unsigned ValueType Promoted
Misc lvalueOf rvalueOf Select select
User-Defined Attributes hasUDA getUDAs getSymbolsByUDA

Source

std/traits.d

@copyrightCopyright The D Language Foundation 2005 - 2009.@licenseBoost License 1.0.@authorsWalter Bright, Tomasz Stachowiak (isExpressions), Andrei Alexandrescu, Shin Fujishiro, Robert Clipsham, David Nadlinger, Kenji Hara, Shoichi Kato
traits
:
(alias template) property_tree_sumtype_variants.FieldNameTuple = std.traits.FieldNameTuple(T)

Get as an expression tuple the names of the fields of a struct, class, or union. This consists of the fields that take up memory space, excluding the hidden fields like the virtual function table pointer or a context pointer for nested types. Inherited fields (for classes) are not included. If T isn't a struct, class, interface or union, an expression tuple with an empty string is returned.

@history
  • Returned AliasSeq!"" for interfaces prior to 2.097

FieldNameTuple
;
@safe: // --- the subject ----------------------------------------------------------- struct
(struct) property_tree_sumtype_variants.Solid
Solid
{
(alias) object.string = string
string
(field) string property_tree_sumtype_variants.Solid.color
color
= "#808080";
float
(field) float property_tree_sumtype_variants.Solid.roughness
roughness
= 0.5;
} struct
(struct) property_tree_sumtype_variants.Gradient
Gradient
{
(alias) object.string = string
string
(field) string property_tree_sumtype_variants.Gradient.from
from
= "#000000";
(alias) object.string = string
string
(field) string property_tree_sumtype_variants.Gradient.to
to
= "#ffffff";
int
(field) int property_tree_sumtype_variants.Gradient.stops
stops
= 2;
} /// A variant whose author forbade default construction. struct
(struct) property_tree_sumtype_variants.Textured

A variant whose author forbade default construction.

Textured
{
(alias) object.string = string
string
(field) string property_tree_sumtype_variants.Textured.path
path
;
@disable this(); this(
property_tree_sumtype_variants.Textured property_tree_sumtype_variants.Textured.this(string p) pure nothrow @nogc ref @safe
string
(parameter) string p
p
) pure nothrow @nogc {
(field) string property_tree_sumtype_variants.Textured.path
path
=
(parameter) string p
p
; }
} /// Variants with no indirections at all — the one case Phobos can overwrite /// safely, and only when every *other* member is equally plain. struct
(struct) property_tree_sumtype_variants.Hidden

Variants with no indirections at all — the one case Phobos can overwrite safely, and only when every other member is equally plain.

Hidden
{ ubyte
(field) ubyte property_tree_sumtype_variants.Hidden.alpha
alpha
;
} struct
(struct) property_tree_sumtype_variants.Flat
Flat
{ int
(field) int property_tree_sumtype_variants.Flat.width
width
,
(field) int property_tree_sumtype_variants.Flat.height
height
;
} alias
(alias) property_tree_sumtype_variants.Fill = std.sumtype.SumType!(Solid, Gradient, Textured)
Fill
=
(struct) std.sumtype.SumType!(property_tree_sumtype_variants.Solid, property_tree_sumtype_variants.Gradient, property_tree_sumtype_variants.Textured)

A tagged union that can hold a single value from any of a specified set of types.

The value in a SumType can be operated on using pattern matching.

To avoid ambiguity, duplicate types are not allowed (but see the "basic usage" example for a workaround).

The special type This can be used as a placeholder to create self-referential types, just like with Algebraic. See the "Recursive SumTypes" example for usage.

A SumType is initialized by default to hold the .init value of its first member type, just like a regular union. The version identifier SumTypeNoDefaultCtor can be used to disable this behavior.

@seeAlgebraic
SumType
!(
(struct) property_tree_sumtype_variants.Solid
Solid
,
(struct) property_tree_sumtype_variants.Gradient
Gradient
,
(struct) property_tree_sumtype_variants.Textured

A variant whose author forbade default construction.

Textured
);
alias
(alias) property_tree_sumtype_variants.PlainFill = std.sumtype.SumType!(Hidden, Flat)
PlainFill
=
(struct) std.sumtype.SumType!(property_tree_sumtype_variants.Hidden, property_tree_sumtype_variants.Flat)

A tagged union that can hold a single value from any of a specified set of types.

The value in a SumType can be operated on using pattern matching.

To avoid ambiguity, duplicate types are not allowed (but see the "basic usage" example for a workaround).

The special type This can be used as a placeholder to create self-referential types, just like with Algebraic. See the "Recursive SumTypes" example for usage.

A SumType is initialized by default to hold the .init value of its first member type, just like a regular union. The version identifier SumTypeNoDefaultCtor can be used to disable this behavior.

@seeAlgebraic
SumType
!(
(struct) property_tree_sumtype_variants.Hidden

Variants with no indirections at all — the one case Phobos can overwrite safely, and only when every other member is equally plain.

Hidden
,
(struct) property_tree_sumtype_variants.Flat
Flat
);
// --- the picker's three questions ------------------------------------------ /// Can a picker offer this variant as a blank slate? `.init` always exists; /// `T()` does not. enum bool
(constant) bool property_tree_sumtype_variants.isBlankConstructable!(property_tree_sumtype_variants.Solid) = true

Can a picker offer this variant as a blank slate? .init always exists; T() does not.

isBlankConstructable
(T) = __traits(compiles, {
(alias) T = property_tree_sumtype_variants.Solid
T
(local variable) property_tree_sumtype_variants.Solid v
v
=
(struct) property_tree_sumtype_variants.Solid
T
(); });
/// Can the switch itself be written in `@safe` code? enum bool
(constant) bool property_tree_sumtype_variants.isSafelyAssignable!(std.sumtype.SumType!(Solid, Gradient, Textured), property_tree_sumtype_variants.Solid) = false

Can the switch itself be written in @safe code?

isSafelyAssignable
(S, T) = __traits(compiles, () @safe {
(alias) S = std.sumtype.SumType!(Solid, Gradient, Textured)
S
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) s
s
=
(struct) property_tree_sumtype_variants.Solid
T
.
(constant) property_tree_sumtype_variants.Solid property_tree_sumtype_variants.Solid.init = Solid("#808080", 0.5F)
init
;
(alias) S = std.sumtype.SumType!(Solid, Gradient, Textured)
S
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) other
other
=
(struct) std.sumtype.SumType!(Solid, Gradient, Textured)
S
.
(constant) std.sumtype.SumType!(Solid, Gradient, Textured) std.sumtype.SumType!(Solid, Gradient, Textured).init = SumType(Storage(Solid("#808080", 0.5F), , ), cast(ubyte)0u)
init
;
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) other
other
=
std.sumtype.SumType!(Solid, Gradient, Textured) std.sumtype.SumType!(property_tree_sumtype_variants.Solid, property_tree_sumtype_variants.Gradient, property_tree_sumtype_variants.Textured).opAssign(property_tree_sumtype_variants.Solid rhs) pure nothrow @nogc ref @system

Assigns a value to a SumType.

If any of the SumType's members other than the one being assigned to contain pointers or references, it is possible for the assignment to cause memory corruption (see the "Memory corruption" example below for an illustration of how). Therefore, such assignments are considered @system.

An individual assignment can be @trusted if the caller can guarantee that there are no outstanding references to any SumType members that contain pointers or references at the time the assignment occurs.

Examples

Memory corruption

This example shows how assignment to a SumType can be used to cause memory corruption in @system code. In @safe code, the assignment s = 123 would not be allowed.

SumType!(int*, int) s = new int;
s.tryMatch!(
    (ref int* p) {
        s = 123; // overwrites `p`
        return *p; // undefined behavior
    }
);
T
.
std.sumtype.SumType!(Solid, Gradient, Textured) std.sumtype.SumType!(property_tree_sumtype_variants.Solid, property_tree_sumtype_variants.Gradient, property_tree_sumtype_variants.Textured).opAssign(property_tree_sumtype_variants.Solid rhs) pure nothrow @nogc ref @system

Assigns a value to a SumType.

If any of the SumType's members other than the one being assigned to contain pointers or references, it is possible for the assignment to cause memory corruption (see the "Memory corruption" example below for an illustration of how). Therefore, such assignments are considered @system.

An individual assignment can be @trusted if the caller can guarantee that there are no outstanding references to any SumType members that contain pointers or references at the time the assignment occurs.

Examples

Memory corruption

This example shows how assignment to a SumType can be used to cause memory corruption in @system code. In @safe code, the assignment s = 123 would not be allowed.

SumType!(int*, int) s = new int;
s.tryMatch!(
    (ref int* p) {
        s = 123; // overwrites `p`
        return *p; // undefined behavior
    }
);
init
;
}); /// The rows this variant contributes, known at compile time.
(alias) object.string = string
string
[]
string[] property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid)() pure nothrow @safe

The rows this variant contributes, known at compile time.

variantRows
(T)() pure nothrow
{
(alias) object.string = string
string
[]
(local variable) string[] rows
rows
;
static foreach (name;
(constant) string property_tree_sumtype_variants.Solid.fun!(color).NameOf = "color"
FieldNameTuple
!T)
(local variable) string[] rows
rows
~=
(constant) string property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid).name = "color"
name
;
return
(local variable) string[] rows
rows
;
} /// Perform the switch. The `@trusted` block is the seam the safety verdict /// forces; its precondition is that nothing holds a reference into the payload. void
void property_tree_sumtype_variants.switchTo!(std.sumtype.SumType!(Solid, Gradient, Textured), property_tree_sumtype_variants.Gradient)(ref std.sumtype.SumType!(Solid, Gradient, Textured) value, property_tree_sumtype_variants.Gradient fresh) pure nothrow @nogc @trusted

Perform the switch. The @trusted block is the seam the safety verdict forces; its precondition is that nothing holds a reference into the payload.

switchTo
(S, T)(ref
(alias) S = std.sumtype.SumType!(Solid, Gradient, Textured)
S
(parameter) std.sumtype.SumType!(Solid, Gradient, Textured) value
value
,
(alias) T = property_tree_sumtype_variants.Gradient
T
(parameter) property_tree_sumtype_variants.Gradient fresh
fresh
) @trusted
{
(parameter) std.sumtype.SumType!(Solid, Gradient, Textured) value
value
=
std.sumtype.SumType!(Solid, Gradient, Textured) std.sumtype.SumType!(property_tree_sumtype_variants.Solid, property_tree_sumtype_variants.Gradient, property_tree_sumtype_variants.Textured).opAssign(property_tree_sumtype_variants.Gradient rhs) pure nothrow @nogc ref @system

Assigns a value to a SumType.

If any of the SumType's members other than the one being assigned to contain pointers or references, it is possible for the assignment to cause memory corruption (see the "Memory corruption" example below for an illustration of how). Therefore, such assignments are considered @system.

An individual assignment can be @trusted if the caller can guarantee that there are no outstanding references to any SumType members that contain pointers or references at the time the assignment occurs.

Examples

Memory corruption

This example shows how assignment to a SumType can be used to cause memory corruption in @system code. In @safe code, the assignment s = 123 would not be allowed.

SumType!(int*, int) s = new int;
s.tryMatch!(
    (ref int* p) {
        s = 123; // overwrites `p`
        return *p; // undefined behavior
    }
);
fresh
;
} void
void D main() @safe
main
()
{
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("Fill = SumType!(Solid, Gradient, Textured)");
void std.stdio.writeln!()() @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
();
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("variant blank? @safe switch? rows");
static foreach (T; Fill.Types)
void std.stdio.writefln!(char, string, string, string, string[])(in char[] fmt, string __param_1, string __param_2, string __param_3, string[] __param_4) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("%-11s %-7s %-14s %s",
(struct) property_tree_sumtype_variants.Solid
T
.
(constant) string property_tree_sumtype_variants.Solid.stringof = "Solid"
stringof
,
(template instance) property_tree_sumtype_variants.isBlankConstructable!(property_tree_sumtype_variants.Solid)
isBlankConstructable
!
(alias) property_tree_sumtype_variants.main.T = property_tree_sumtype_variants.Solid
T
? "yes" : "no",
(template instance) property_tree_sumtype_variants.isSafelyAssignable!(std.sumtype.SumType!(Solid, Gradient, Textured), property_tree_sumtype_variants.Solid)
isSafelyAssignable
!(
(alias) property_tree_sumtype_variants.Fill = std.sumtype.SumType!(Solid, Gradient, Textured)
Fill
,
(alias) property_tree_sumtype_variants.main.T = property_tree_sumtype_variants.Solid
T
) ? "yes" : "no",
string[] property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid)() pure nothrow @safe

The rows this variant contributes, known at compile time.

variantRows
!
(alias) property_tree_sumtype_variants.main.T = property_tree_sumtype_variants.Solid
T
());
void std.stdio.writeln!()() @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
();
void std.stdio.writefln!(char, string)(in char[] fmt, string __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("a sum of indirection-free variants is @safe to switch: %s",
(template instance) property_tree_sumtype_variants.isSafelyAssignable!(std.sumtype.SumType!(Hidden, Flat), property_tree_sumtype_variants.Hidden)
isSafelyAssignable
!(
(alias) property_tree_sumtype_variants.PlainFill = std.sumtype.SumType!(Hidden, Flat)
PlainFill
,
(struct) property_tree_sumtype_variants.Hidden

Variants with no indirections at all — the one case Phobos can overwrite safely, and only when every other member is equally plain.

Hidden
) ? "yes" : "no");
void std.stdio.writeln!()() @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
();
// A switch is a whole-value replacement: the old variant's rows do not // migrate, and no per-field state can be carried across by the component.
(alias) property_tree_sumtype_variants.Fill = std.sumtype.SumType!(Solid, Gradient, Textured)
Fill
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fill
fill
=
(struct) property_tree_sumtype_variants.Solid
Solid
("#ff0000", 0.2);
void std.stdio.writefln!(char, string[])(in char[] fmt, string[] __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("before: rows=%s",
string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @safe
rowsOf
(
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fill
fill
));
void property_tree_sumtype_variants.switchTo!(std.sumtype.SumType!(Solid, Gradient, Textured), property_tree_sumtype_variants.Gradient)(ref std.sumtype.SumType!(Solid, Gradient, Textured) value, property_tree_sumtype_variants.Gradient fresh) pure nothrow @nogc @trusted

Perform the switch. The @trusted block is the seam the safety verdict forces; its precondition is that nothing holds a reference into the payload.

switchTo
(
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fill
fill
,
(struct) property_tree_sumtype_variants.Gradient
Gradient
.
(constant) property_tree_sumtype_variants.Gradient property_tree_sumtype_variants.Gradient.init = Gradient("#000000", "#ffffff", 2)
init
); // what a picker does on "choose Gradient"
void std.stdio.writefln!(char, string[])(in char[] fmt, string[] __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("after: rows=%s",
string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @safe
rowsOf
(
(local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fill
fill
));
void std.stdio.writeln!()() @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
();
// `.init` stays readable even for the variant a picker must not offer blank.
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("Textured.init.path is null: %s",
(struct) property_tree_sumtype_variants.Textured

A variant whose author forbade default construction.

Textured
.
(constant) property_tree_sumtype_variants.Textured property_tree_sumtype_variants.Textured.init = Textured(null)
init
.
(field) string property_tree_sumtype_variants.Textured.path
path
is null);
}
(alias) object.string = string
string
[]
string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @safe
rowsOf
(in
(alias) property_tree_sumtype_variants.Fill = std.sumtype.SumType!(Solid, Gradient, Textured)
Fill
(parameter) const(std.sumtype.SumType!(Solid, Gradient, Textured)) fill
fill
)
=>
(parameter) const(std.sumtype.SumType!(Solid, Gradient, Textured)) fill
fill
.
string[] std.sumtype.match!(function (ref const(property_tree_sumtype_variants.Solid) _) pure nothrow @safe => variantRows(), function (ref const(property_tree_sumtype_variants.Gradient) _) pure nothrow @safe => variantRows(), function (ref const(property_tree_sumtype_variants.Textured) _) pure nothrow @safe => variantRows()).match!(const(std.sumtype.SumType!(Solid, Gradient, Textured)))(ref const(std.sumtype.SumType!(Solid, Gradient, Textured)) __param_0) pure nothrow @safe

Calls a type-appropriate function with the value held in a SumType.

For each possible type the SumType can hold, the given handlers are checked, in order, to see whether they accept a single argument of that type. The first one that does is chosen as the match for that type. (Note that the first match may not always be the most exact match. See "Avoiding unintentional matches" for one common pitfall.)

Every type must have a matching handler, and every handler must match at least one type. This is enforced at compile time.

Handlers may be functions, delegates, or objects with opCall overloads. If a function with more than one overload is given as a handler, all of the overloads are considered as potential matches.

Templated handlers are also accepted, and will match any type for which they can be implicitly instantiated. (Remember that a function literal without an explicit argument type is considered a template.)

If multiple SumTypes are passed to match, their values are passed to the handlers as separate arguments, and matching is done for each possible combination of value types. See "Multiple dispatch" for an example.

Examples

Avoiding unintentional matches

Sometimes, implicit conversions may cause a handler to match more types than intended. The example below shows two solutions to this problem.

alias Number = SumType!(double, int);

Number x;

// Problem: because int implicitly converts to double, the double
// handler is used for both types, and the int handler never matches.
assert(!__traits(compiles,
    x.match!(
        (double d) => "got double",
        (int n) => "got int"
    )
));

// Solution 1: put the handler for the "more specialized" type (in this
// case, int) before the handler for the type it converts to.
assert(__traits(compiles,
    x.match!(
        (int n) => "got int",
        (double d) => "got double"
    )
));

// Solution 2: use a template that only accepts the exact type it's
// supposed to match, instead of any type that implicitly converts to it.
alias exactly(T, alias fun) = function (arg)
{
    static assert(is(typeof(arg) == T));
    return fun(arg);
};

// Now, even if we put the double handler first, it will only be used for
// doubles, not ints.
assert(__traits(compiles,
    x.match!(
        exactly!(double, d => "got double"),
        exactly!(int, n => "got int")
    )
));

Multiple dispatch

Pattern matching can be performed on multiple SumTypes at once by passing handlers with multiple arguments. This usually leads to more concise code than using nested calls to match, as show below.

struct Point2D { double x, y; }
struct Point3D { double x, y, z; }

alias Point = SumType!(Point2D, Point3D);

version (none)
{
    // This function works, but the code is ugly and repetitive.
    // It uses three separate calls to match!
    @safe pure nothrow @nogc
    bool sameDimensions(Point p1, Point p2)
    {
        return p1.match!(
            (Point2D _) => p2.match!(
                (Point2D _) => true,
                _ => false
            ),
            (Point3D _) => p2.match!(
                (Point3D _) => true,
                _ => false
            )
        );
    }
}

// This version is much nicer.
@safe pure nothrow @nogc
bool sameDimensions(Point p1, Point p2)
{
    alias doMatch = match!(
        (Point2D _1, Point2D _2) => true,
        (Point3D _1, Point3D _2) => true,
        (_1, _2) => false
    );

    return doMatch(p1, p2);
}

Point a = Point2D(1, 2);
Point b = Point2D(3, 4);
Point c = Point3D(5, 6, 7);
Point d = Point3D(8, 9, 0);

assert( sameDimensions(a, b));
assert( sameDimensions(c, d));
assert(!sameDimensions(a, c));
assert(!sameDimensions(d, b));
@returnsThe value returned from the handler that matches the currently-held type.@see

visit

The actual match function.

@paramargs One or more SumType objects.
match
!(
(ref const Solid _) => variantRows!Solid(), (ref const Gradient _) => variantRows!Gradient(), (ref const Textured _) => variantRows!Textured());