Earlier, we introduced union types. Unions make it easy to define a fixed-number of variant structs that are all the same size. Traits may be used to define variant struct types whose sizes are not necessarily identical.

There are two flavors of trait-based variant types: closed and open.

Closed Trait-based Variant Types

Closed traits support only a fixed number of variant types, completely enumerated within the definition of the trait. The list of variant types cannot be extended beyond this. In this sense, it is closed (or sealed).

The definition for closed trait-based variant types looks nearly identical to a union type definition. The only difference is the definition begins with trait instead of union:

trait Event:
	time datetime
	struct ButtonEvent:
		button u8
		pushed Bool
	struct KeyEvent:
		key Unicode
	struct QuitEvent

An initial value is created using one of the variant types' constructor or initializers, exactly the same as for unions.

When the base type is a trait (instead of union) ButtonEvent, KeyEvent, and QuitEvent values will have different sizes. Since they have different sizes, we cannot swap out a concrete value of one type for a value of another type in the same allocated memory.

Similarly, it is illegal to define a variable as holding a value of some trait type:

imm event Event = ...

We can only refer to trait-based values by reference:

imm event &Event = &someKeyEvent

Notice how we safely coerced a reference to a variant type automatically into a reference to the trait.

When working with a trait reference, we can only access a shared field or call a shared method (for example, event.time). This is possible because all possible variant types implement them.

To convert such a reference back to the concrete type it holds, pattern matching must be employed. This uses the hidden tag field to determine the concrete type.

Virtual Method dispatch

Sometimes we want all variant types to support a common set of methods whose signature is identical, but whose implementation varies according to the variant type. In such situations, the shared method is declared but not implemented as part of the trait. Then every variant type provides its own distinct implementation of that method.

This is illustrated by the 'double' method in this example:

trait Number:
  fn double(self &mut Self)
  struct Real:
	n f32
	fn double(self &mut Self):
	  n *= 2
  struct Complex:
    r f32
	i f32
	fn double(self &mut Self):
	  r *= 2
	  i *= 2

When we call the double method on a reference to a Number (e.g., number.double(), the correct double method is automatically invoked based on which variant type the reference points to. In effect, the tag field is used to determine the variant type. That leads to the right vtable which then points to the method to call.

This kind of virtual method dispatch can be accomplished in exactly the same way with union type variants.

Open Trait-based Variant Types

Where open traits differ from closed traits lies in the fact that the list of possible variants can be arbitrarily extended by other modules. This versatility carries a cost: we cannot know at compile-time how many variants there will be, and therefore we cannot assign each of them a guaranteed-unique tag value. As a result, open traits have no implicit (or explicit) tag field.

With open trait-based variant types, the variant types are defined separately from the trait, rather than inside. These variants may be defined anywhere, even in a different module than the trait.

To show that a variant type belongs to a specific trait, it explicitly specifies that it extends the named trait.

An open trait version of the Event example would lay out like this:

trait Event:
	time datetime
struct ButtonEvent extends Event:
	button u8
	pushed Bool

struct KeyEvent extends Event:
	key Unicode

struct QuitEvent extends Event

The absence of a tag field means that we cannot do pattern matching or method dispatch on a regular reference to an open trait. For those capabilities, we first need to obtain and then use a virtual reference to the trait.

Choosing the best variant type flavor

Cone supports three flavors of variant types: unions, closed traits and open traits. How do you know which one to choose?