std.sumtype - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Forums
  - IRC
  - Community Discord
  - Wiki
  - GitHub
  - Issues
  - Get involved
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# 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.

License:
Boost License 1.0
Authors:
Paul Backus

Source std/sumtype.d

Examples:

### Basic usage

    import std.math.operations : isClose;

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

    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.degrees,
                (Celsius c) => c.degrees * 9.0/5 + 32,
                (Kelvin k) => k.degrees * 9.0/5 - 459.4
            )
        );
    }

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

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

    freeze(t1);
    assert(toFahrenheit(t1).degrees.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));

Examples:

### Introspection-based matching

In the length and horiz functions below, the handlers for match do not specify the types of their arguments. Instead, matching is done based on how the argument is used in the body of the handler: any type with x and y properties will be matched by the rect handlers, and any type with r and theta properties will be matched by the polar handlers.

    import std.math.operations : isClose;
    import std.math.trigonometry : cos;
    import std.math.constants : PI;
    import std.math.algebraic : sqrt;

    struct Rectangular { double x, y; }
    struct Polar { double r, theta; }
    alias Vector = SumType!(Rectangular, Polar);

    double length(Vector v)
    {
        return v.match!(
            rect => sqrt(rect.x^^2 + rect.y^^2),
            polar => polar.r
        );
    }

    double horiz(Vector v)
    {
        return v.match!(
            rect => rect.x,
            polar => polar.r * cos(polar.theta)
        );
    }

    Vector u = Rectangular(1, 1);
    Vector v = Polar(1, PI/4);

    assert(length(u).isClose(sqrt(2.0)));
    assert(length(v).isClose(1));
    assert(horiz(u).isClose(1));
    assert(horiz(v).isClose(sqrt(0.5)));

Examples:

### Arithmetic expression evaluator

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];

    writeln(eval(*myExpr, myEnv)); // 11
    writeln(pprint(*myExpr)); // "(a + (2 * b))"

struct `This`;
  Placeholder used to refer to the enclosing `SumType`.
struct `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 "Arithmetic expression evaluator" 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.
  See Also:
  std.variant.Algebraic
  alias `Types` = AliasSeq!(ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType));
    The types a SumType can hold.
  this(T `value`);
  const this(const(T) `value`);
  immutable this(immutable(T) `value`);
  inout this(Value)(Value `value`)
  if (is(Value == DeducedParameterType!(inout(T))));
    Constructs a SumType holding a specific value.
  inout this(ref inout(SumType) `other`);
    Constructs a SumType that's a copy of another SumType.
  ref SumType `opAssign`(T `rhs`);
    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
            }
        );

  ref SumType `opAssign`(ref SumType `rhs`);
    Copies the value from another SumType into this one.
    See the value-assignment overload for details on @safety.
    Copy assignment is @disabled if any of Types is non-copyable.
  ref SumType `opAssign`(SumType `rhs`);
    Moves the value from another SumType into this one.
    See the value-assignment overload for details on @safety.
  bool `opEquals`(this This, Rhs)(auto ref Rhs `rhs`)
  if (!is(CommonType!(This, Rhs) == void));
    Compares two SumTypes for equality.
    Two SumTypes are equal if they are the same kind of SumType, they contain values of the same type, and those values are equal.
  string `toString`(this This)();
    Returns a string representation of the SumType's current value.
    Not available when compiled with -betterC.
  void `toString`(this This, Sink, Char)(ref Sink `sink`, const ref FormatSpec!Char `fmt`);
    Handles formatted writing of the SumType's current value.
    Not available when compiled with -betterC.
    Parameters:

    Sink `sink` | Output range to write to.
    FormatSpec!Char `fmt` | Format specifier to use.

    See Also:
    std.format.formatValue
  const size_t `toHash`();
    Returns the hash of the SumType's current value.
    Not available when compiled with -betterC.
enum bool `isSumType`(T);
  True if T is a `SumType` or implicitly converts to one, otherwise false.
  Examples:

      static struct ConvertsToSumType
      {
          SumType!int payload;
          alias payload this;
      }

      static struct ContainsSumType
      {
          SumType!int payload;
      }

      assert(isSumType!(SumType!int));
      assert(isSumType!ConvertsToSumType);
      assert(!isSumType!ContainsSumType);

template `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. See "Introspection-based matching" for an example of templated handler usage.
  If multiple `SumType`s 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.
  Returns:
  The value returned from the handler that matches the currently-held type.
  See Also:
  std.variant.visit
  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")
          )
      ));

  Examples:

  ### 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));

  ref auto `match`(SumTypes...)(auto ref SumTypes `args`)
  if (allSatisfy!(isSumType, SumTypes) && (`args`.length > 0));
    The actual `match` function.
    Parameters:

    SumTypes `args` | One or more `SumType` objects.

template `tryMatch`(handlers...)
  Attempts to call a type-appropriate function with the value held in a `SumType`, and throws on failure.
  Matches are chosen using the same rules as `match`, but are not required to be exhaustive—in other words, a type (or combination of types) is allowed to have no matching handler. If a type without a handler is encountered at runtime, a `MatchException` is thrown.
  Not available when compiled with -betterC.
  Returns:
  The value returned from the handler that matches the currently-held type, if a handler was given for that type.
  Throws:
  `MatchException`, if the currently-held type has no matching handler.
  See Also:
  std.variant.tryVisit
  ref auto `tryMatch`(SumTypes...)(auto ref SumTypes `args`)
  if (allSatisfy!(isSumType, SumTypes) && (`args`.length > 0));
    The actual `tryMatch` function.
    Parameters:

    SumTypes `args` | One or more `SumType` objects.

class `MatchException`: object.Exception;
  Thrown by `tryMatch` when an unhandled type is encountered.
  Not available when compiled with -betterC.
  pure nothrow @nogc @safe this(string `msg`, string `file` = __FILE__, size_t `line` = __LINE__);
template `canMatch`(alias handler, Ts...) if (Ts.length > 0)
  True if handler is a potential match for Ts, otherwise false.
  See the documentation for `match` for a full explanation of how matches are chosen.
  Examples:

      alias handleInt = (int i) => "got an int";

      assert( canMatch!(handleInt, int));
      assert(!canMatch!(handleInt, string));

Copyright © 1999-2023 by the D Language Foundation | Page generated by Ddoc on Wed Mar 1 14:57:18 2023