Creating a tagged dynamic type

D's structs are a good foundation to create a dynamic type. We'll create a tagged union to serve as a starting dynamic type. This is the technique used to implement the library jsvar type that we used in a previous chapter. Here, we'll only support int and string.

How to do it…

We will execute the following steps to create a tagged dynamic type:

  1. Write a struct with two members: a tag that holds the current type and a union that holds the possible types.
  2. Make the data private and add checked getter and setter functions.
  3. Add a constructor that takes the possible types. You may write it in terms of opAssign.
  4. Add type coercion if you want weak typing in the getter function; otherwise, throw an exception on type mismatches.
  5. Implement other methods or operators you want.

The following is the code to create a tagged dynamic type:

struct Dynamic {
  enum Type { integer, string }
  private Type type;
  private union {
    int integer;
    string str;
  }

  this(T)(T t) { // easy construction
    this = t;
  }

  // assignment of our supported types
  Dynamic opAssign(inti) {
    type = Type.integer;
    integer = i;
    return this;
  }

  Dynamic opAssign(string i) {
    type = Type.string;
    str = i;
    return this;
  }

  // getting our types back out into static variables
  // here, we used the coercion option instead of throwing if
  // we had the wrong type
  int asInteger() {
    final switch(type) {
      case Type.integer:
        return integer;
      case Type.string:
        import std.conv;
        return to!int(str);
    }
  }

  string asString() {
    final switch(type) {
      case Type.string:
        return str;
      case Type.integer:
        import std.conv;
        return to!string(integer);
    }
  }

  // we may also implement other operators for more integration
}

How it works…

Dynamic types are usually implemented using the tagged union technique used here. A union is a group of variables which share a block of overlapped memory. Unless all the variables of the union are valid simultaneously (which we'll look at in the following recipe), it is important to know which member is currently active. That's where the type member comes into play.

The other parts of the code are things we've already seen. The opAssign function implements the = operator with other types. The constructor implements by declaring the struct with an argument list of other types. The asInteger and asString methods are just regular functions.

There's one interesting bit we use in those methods: the final switch. A final switch is guaranteed at compile time to cover every member of the enum. With an enum, this means the build will fail if a member is not handled by a case, making the final switch resilient to changes. If a new member is added to the enum, the compiler will help you remember to add a handling case to each and every switch that uses it.

There's more…

Phobos' std.variant provides a tagged union, Variant, that uses a large union as well as a pointer to an even larger type, making it capable of holding almost anything. Variant doesn't provide weak typing or full operator overloading. However, since it can hold and offer nearly any type with proper runtime checking, you may use it when you need a generic runtime variable, such as an array of mixed type elements.

Variant uses method templates and generated function pointers to handle the types rather than writing out a list manually. We'll look at these techniques in depth in Chapter 9, Code Generation, and Chapter 8, Reflection.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.140.204