Note: None of this capability is currently implemented.

From a type perspective, the structure of a struct or array value is quite fixed. This is restrictive when we need values whose structure can vary based on runtime logic. This is what enum (and union-based) types address. They allow a value's type structure to vary as signalled by its captured "tag".

Enums hold a single value whose type could be one of several variations. For example:

enum Number
	Integer i32
	Float   f32

This defines a new enum type called Number which holds a single numeric value. That value could be an integer or a floating-point number, but not both. Unlike with structs, Integer and Float are not separate fields stored in different locations. They are instead two possible overlays on a single memory location.

Under the covers, enum values also hold a hidden field called the "tag". In this example, the tag indicates whether the value currently holds an integer (tag field = 0) or a floating-point number (tag field = 1).

Declaration

As the example shows, an enum type declaration resembles a struct declaration. It begins by giving a name to this new, custom-defined type. The variant types options are listed within, each specifying the option's:

This declaration specifies three variant types:

enum OddEnum
	None          // No value for this variant
	SomeInt i32   // Value is an integer
	PointStruct   // Value is a struct holding two floats
		x f32
		y f32

Note: enums can be used to associate names to constant numbers. Just leave out type information:

enum Colors
	Red   = 0xFF0000
	Green = 0x00FF00
	Blue  = 0x0000FF

Initialization

An enum initial value is built using the enum constructor:

mut oddval = OddEnum::None
oddval = OddEnum::SomeInt[34]
oddval = OddEnum::PointStruct[x: 4., y: 8.]

Copying

An enum value may be passed around, by copy, just like any other value. This is possible because all variant values of a specific enum type are the same size. The enum type's size is effectively the size of its largest variant type plus the size of its hidden tag field.

Value Access

To safely gain access to the differently-typed value enclosed with an enum value, we must use pattern matching. As a brief peek into the versatile power of partial pattern matching, consider this example:

match oddval
	~~ intval = SomeInt:     intval
	~~ point = PointStruct:  i32[point.x * point.y]
	None:                    0

The match statement allows us to determine which type of data this OddEnum value holds. If it is an integer, it will match on the first option and (as part of the match) copy that integer into a new variable called intval. For this variant, the logic indicates that the match expression should evaluate to this integer's value. And so it goes for matching against any of the other variants.

One may also match an enum value against a single variant using an if statement:

sum += intval if oddval ~~ intval=SomeInt

Note:If an enum value is being matched against a variant that has no value (e.g., none above), the equivalence operator == may be used instead of the partial match operator ~~, if desired.

Comparing

An enum value may only be compared for equivalence (and not order). The equivalence check compares the tag fields first. If they are equal, the appropriately-typed interior values are compared using the == method.

Nullable Types

A common enum type is the nullable type. This is needed whenever we either have a value of some type or else we have the absence of a value (called null). Nullable types are needed so often that special syntax exist to make it easier to use.

To declare a nullable type, simply put a question mark before the type. For example:

mut maybeInt ?i32 = null   // Initial value indicates the absence of a value
if maybeInt == null
  maybeInt = 4             // Give it the value 4

If the type of a declared variable is omitted but an initial value is specified, the initial value needs to be wrapped in the some constructor:

mut maybeInt = some[4]

Nullable References Although we have not covered references yet, it is worth nothing here that nullable references take no more space than regular references. The null reference value is just a non-addressable address (typically, 0).

Use of nullable values

All the usual capabilities available to enum values are also available to nullable values, including copying, comparing and pattern matching. Additionally, a number of pre-defined methods are available for use on nullable values.

Two special operators exist to make it more convenient to do certain common operations on a nullable value. The double exclamation operator is used when we know a nullable value is not null and we want to keep using it as part of an expression:

imm x = maybeInt!! + 12

A special method call operator is used to allow us to safely call a method on a nullable value:

imm x = maybePoint?.dist

This is identical to:

imm x = match maybePoint
  null: null
  ~~ p = Point: some(p.dist)

_