#!/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_variantsThe 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:
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.
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) stdstd.(module) std.stdioCategory 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:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
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);
}
}
writeln;
import (package) stdstd.(module) std.sumtypeSumType 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
Memory corruption (why assignment can be @system)
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))");
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.
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.
SumType;
import (package) stdstd.(module) std.traitsTemplates 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
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.
FieldNameTuple;
@safe:
// --- the subject -----------------------------------------------------------
struct (struct) property_tree_sumtype_variants.SolidSolid
{
(alias) object.string = stringstring (field) string property_tree_sumtype_variants.Solid.colorcolor = "#808080";
float (field) float property_tree_sumtype_variants.Solid.roughnessroughness = 0.5;
}
struct (struct) property_tree_sumtype_variants.GradientGradient
{
(alias) object.string = stringstring (field) string property_tree_sumtype_variants.Gradient.fromfrom = "#000000";
(alias) object.string = stringstring (field) string property_tree_sumtype_variants.Gradient.toto = "#ffffff";
int (field) int property_tree_sumtype_variants.Gradient.stopsstops = 2;
}
/// A variant whose author forbade default construction.
struct (struct) property_tree_sumtype_variants.TexturedA variant whose author forbade default construction.
Textured
{
(alias) object.string = stringstring (field) string property_tree_sumtype_variants.Textured.pathpath;
@disable this();
this(property_tree_sumtype_variants.Textured property_tree_sumtype_variants.Textured.this(string p) pure nothrow @nogc ref @safestring (parameter) string pp) pure nothrow @nogc { (field) string property_tree_sumtype_variants.Textured.pathpath = (parameter) string pp; }
}
/// 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.HiddenVariants 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.alphaalpha;
}
struct (struct) property_tree_sumtype_variants.FlatFlat
{
int (field) int property_tree_sumtype_variants.Flat.widthwidth, (field) int property_tree_sumtype_variants.Flat.heightheight;
}
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.
SumType!((struct) property_tree_sumtype_variants.SolidSolid, (struct) property_tree_sumtype_variants.GradientGradient, (struct) property_tree_sumtype_variants.TexturedA 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.
SumType!((struct) property_tree_sumtype_variants.HiddenVariants 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.FlatFlat);
// --- 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) = trueCan 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.SolidT (local variable) property_tree_sumtype_variants.Solid vv = (struct) property_tree_sumtype_variants.SolidT(); });
/// 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) = falseCan 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) ss = (struct) property_tree_sumtype_variants.SolidT.(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) otherother = (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) otherother = 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 @systemAssigns 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 @systemAssigns 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 = stringstring[] string[] property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid)() pure nothrow @safeThe rows this variant contributes, known at compile time.
variantRows(T)() pure nothrow
{
(alias) object.string = stringstring[] (local variable) string[] rowsrows;
static foreach (name; (constant) string property_tree_sumtype_variants.Solid.fun!(color).NameOf = "color"FieldNameTuple!T)
(local variable) string[] rowsrows ~= (constant) string property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid).name = "color"name;
return (local variable) string[] rowsrows;
}
/// 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 @trustedPerform 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) valuevalue, (alias) T = property_tree_sumtype_variants.GradientT (parameter) property_tree_sumtype_variants.Gradient freshfresh) @trusted
{
(parameter) std.sumtype.SumType!(Solid, Gradient, Textured) valuevalue = 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 @systemAssigns 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() @safemain()
{
void std.stdio.writeln!string(string __param_0) @safeEquivalent 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);
}
}
writeln("Fill = SumType!(Solid, Gradient, Textured)");
void std.stdio.writeln!()() @safeEquivalent 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);
}
}
writeln();
void std.stdio.writeln!string(string __param_0) @safeEquivalent 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);
}
}
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) @safeEquivalent to writef(fmt, args, '\n').
writefln("%-11s %-7s %-14s %s", (struct) property_tree_sumtype_variants.SolidT.(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.SolidT ? "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.SolidT) ? "yes" : "no",
string[] property_tree_sumtype_variants.variantRows!(property_tree_sumtype_variants.Solid)() pure nothrow @safeThe rows this variant contributes, known at compile time.
variantRows!(alias) property_tree_sumtype_variants.main.T = property_tree_sumtype_variants.SolidT());
void std.stdio.writeln!()() @safeEquivalent 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);
}
}
writeln();
void std.stdio.writefln!(char, string)(in char[] fmt, string __param_1) @safeEquivalent 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.HiddenVariants 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!()() @safeEquivalent 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);
}
}
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) fillfill = (struct) property_tree_sumtype_variants.SolidSolid("#ff0000", 0.2);
void std.stdio.writefln!(char, string[])(in char[] fmt, string[] __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("before: rows=%s", string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @saferowsOf((local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fillfill));
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 @trustedPerform 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) fillfill, (struct) property_tree_sumtype_variants.GradientGradient.(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) @safeEquivalent to writef(fmt, args, '\n').
writefln("after: rows=%s", string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @saferowsOf((local variable) std.sumtype.SumType!(Solid, Gradient, Textured) fillfill));
void std.stdio.writeln!()() @safeEquivalent 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);
}
}
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) @safeEquivalent to writef(fmt, args, '\n').
writefln("Textured.init.path is null: %s", (struct) property_tree_sumtype_variants.TexturedA 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.pathpath is null);
}
(alias) object.string = stringstring[] string[] property_tree_sumtype_variants.rowsOf(in std.sumtype.SumType!(Solid, Gradient, Textured) fill) @saferowsOf(in (alias) property_tree_sumtype_variants.Fill = std.sumtype.SumType!(Solid, Gradient, Textured)Fill (parameter) const(std.sumtype.SumType!(Solid, Gradient, Textured)) fillfill)
=> (parameter) const(std.sumtype.SumType!(Solid, Gradient, Textured)) fillfill.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 @safeCalls 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));
match!(
(ref const Solid _) => variantRows!Solid(),
(ref const Gradient _) => variantRows!Gradient(),
(ref const Textured _) => variantRows!Textured());