import variant; import std.stdio; // Setting it up for examples below void showOverload(int n) { writefln("int"); } void showOverload(char[] c) { writefln("char[]"); } void main() { // The base data-structure is the Variant struct, which takes a list of types: alias Variant!(int, long, char[]) MyVariant; // MyVariant is a type which can be assigned ints, char[]s, or Foo's. // This data-type has opAssign overloaded, to allow: MyVariant v; v = 5; // it now holds an int v = 5L; // it now holds a long v = "World!"; // now holds a char[] // The point of interest is that it keeps track of the stored type, and pattern matching (similar to a switch statement) // can be used to access the data in a type-safe way: mixin(Match!(v, Case!("int n", `writefln("Twice your number is %d", 2*n);`), Case!("char[] s", `writefln("Hello ", s);`))); // The Match!() statement ensures that the value is only used as an appropriate type. // However, it needn't be used with the exact type. Instead (just as a proof of concept), if no exact match is found, // it follows D's overloading rules (to a certain extent), and it will match with any type which can be implicitly // converted to the type specified in the pattern, so mixin(Match!(v, Case!("real r", `writefln("Your value is a long or an int with a value of ", r);`))); // Also as a neat thing, we can express multiple cases with one pattern. The supplied code is then inserted once for each // different type specified by the pattern (similar to the quasi-static foreach -- that's the main reason I put it in): mixin(Match!(v, Case!("{int|char[]} n", `showOverload(n);`))); // The above code will print "int" if matched with an int, and "char[]" if matched with a char[] // If a pattern is unreachable, you will be told at compile time: mixin(Match!(v, Case!("real r", `writefln("Matches int and long");`) // , Case!("int n", `writefln("Matches int");`) // This line would give an error: // "Unreachable case statement: int n" )); // Finally, you can use MatchStrict!() to express a match statement with the requirement that every possible type is // handled. This is useful if the possible data-types will change, and you want to be warned in the future if you don't handle // all of them. Other than this requirement, MatchStrict!() behaves just like Match!() mixin(MatchStrict!(v, Case!("{int|char[]} a", `// Do nothing`), Case!("long l", `writefln("Aha! A long");`) // Commenting this line out would cause an error at compile-time: // "Not all cases expressed in match statement" )); }