Slide 23 of 74

Declared types

A struct
type User {
	id:   Int
	name: Str
}

proc main(): void {
	u := User(1, "ada")
	echo u.name
	echo User(name: "grace", id: 2).id   // by name, in any order
}
A tagged union, and its shared fields
type Event {
	at: Int                  // outside every variant: every one has it
	Opened { by: Str }
	Closed
}

proc main(): void {
	e := Event.Opened("ada", 5)   // the variant's own field first, then `at`
	echo e.at                      // 5 — a shared field is read straight off
	// echo e.by                   // compile error: `by` belongs to Opened

	for each ev in [e, Event.Closed(9)] {
		echo ev.at
	}
}
A type that holds itself
type Expr {
	Num { value: Int }
	Add { left: Expr, right: Expr }
}

type Folder {
	name: Str
	children: Folder[dyn]    // through a vector
}

proc main(): void {
	sum := Expr.Add(Expr.Num(1), Expr.Num(2))
	echo sum
	echo Folder("root", [Folder("docs", [])]).name
}

A type with no variants is a struct; with variants, a tagged union. A value is built by calling the type or the variant — User(1, "ada"), Shape.Circle(5) — and a variant that carries nothing is still called: Shape.Point(), since Shape.Point on its own names the variant rather than a value of it. Fields may be passed by name, in any order. There is no subtyping and no interface: a variant is a way of building and matching a union, not a type of its own.

A field declared outside every variant is added to all of them, after each variant's own fields, and it is the one kind of field a union value has: event.at reads it straight off. Every other field belongs to its variant and is reached by matching with is, on the next slides — even one that every variant happens to declare under the same name. A union value is built whole, so no field of one is assigned on its own.

A type may hold itself through a variant, which is what a tree needs, or through a vector. A struct that holds itself directly, or through a Result, could never be finished, and is refused.