Unions

Conjure supports two different kinds of unions: tagged unions (also known as sum types or algebraic data types) and untagged unions (similar to C-style unions).

Tagged Unions (Sum Types) Proposed

Sum types, or tagged unions, allow you to define a type that can hold one of several different variants, each potentially with its own associated data. This is useful for representing data that can take on multiple forms while ensuring type safety.

To create a tagged union, just create a const value containing a set of types joined together using the pipe (|) operator.

struct Circle { radius f64 }
struct Rectangle { width f64, height f64 }
struct Triangle { base f64, height f64 }

const Shape = Circle | Rectangle | Triangle

var myShape Shape = Circle{ radius = 5.0 }

Accessing Tagged Union Variants Proposed

You can use the is operator to check which variant of the union is currently held.

if myShape is Circle {
	// `myShape` is treated as a `Circle` here
	io.println("Circle with radius #{myShape.radius}")
} else if myShape is Rectangle {
	// `myShape` is treated as a `Rectangle` here
	io.println("Rectangle with width #{myShape.width} and height #{myShape.height}")
} else if myShape is Triangle {
	// `myShape` is treated as a `Triangle` here
	io.println("Triangle with base #{myShape.base} and height #{myShape.height}")
}

Pattern-Matching Tagged Unions Proposed

You can then use pattern matching with the when expression/statement to handle each variant of the union.

when myShape {
	case Circle:
		// `myShape` is treated as a `Circle` here
		io.println("Circle with radius #{myShape.radius}")
	case Rectangle as rect:
		// a new variable `rect` of type `Rectangle` is created
		io.println("Rectangle with width #{rect.width} and height #{rect.height}")
	case Triangle:
		// `myShape` is treated as a `Triangle` here
		io.println("Triangle with base #{myShape.base} and height #{myShape.height}")
}

Untagged Unions

The alternative to tagged unions are untagged unions, which are similar to C-style unions. They allow you to define a type that can hold one of several different variants, but without any tagging or type safety. This means that you need to manage which variant is currently being used manually.

// A tagged union representing a block of data that can
// either be an integer, a float, or a struct.
union DataBlock {
	int   i32
	float f64
	point struct{ x f64, y f64 }
}

var block DataBlock

// Assign an integer to the union
block = i32(42)

// Read the integer from the union
var intValue = block.int

// Assign a float to the union
block = f64(3.14)