Records and unions
A rec is a named collection of typed fields, each with its own storage. A uni overlaps its fields in the same memory. Together they compose every aggregate mach has, including discriminated values.
Records
A rec lays out its fields side by side. Each field has independent storage, and the record's size is the sum of the field sizes plus any padding the compiler inserts for alignment. A record may be generic over type parameters.
rec NAME {
field1: type;
field2: type;
...
}
rec NAME[T, U] { ... } # generic over type parameters
pub rec Point {
x: i64;
y: i64;
}
pub rec Pair[T, U] {
left: T;
right: U;
}
Construction and access
A record literal names the type and supplies each field by name. Read a field with ..
val p: Point = Point{ x: 1, y: 2 };
val q: Pair[i64, u8] = Pair[i64, u8]{ left: 5, right: 6u8 };
val n: i64 = p.x; # field access via .
Memory layout
By default the compiler may insert padding between fields to satisfy each field's alignment, following the natural C-style rule. The #[align(N)] decorator on a record raises its minimum type alignment to N bytes, where N is a power of two.
#[align(16)]
pub rec Aligned {
a: u8;
b: i64;
}
Disabling padding entirely (a packed layout for binary-protocol or on-disk structs) is not yet available. Field-to-field padding always follows the natural alignment rule.
Set a type's alignment with the #[align(N)] decorator.
Unions
A uni is a collection of named fields that share the same memory. Writing one field overwrites whatever bytes the others held. A union's size is the size of its largest field plus any alignment padding. Unions may be generic.
pub uni Number {
i: i64;
f: f64;
}
pub uni Maybe[T] {
some: T;
none: u8;
}
The compiler does not track which field of a union is live. Reading a field other than the one last written reinterprets raw bytes; keeping the active field straight is the programmer's responsibility.
Discriminated values
Mach has no tagged-union construct and no pattern-matching dispatch. A discriminated value composes from a rec that carries a discriminator alongside a uni payload.
rec Value {
kind: ValueKind;
data: uni { i: i64; f: f64; }
}
Consumers read kind, then access the matching data field through ordinary if/or chains. The compiler does not verify that kind and data agree; keeping them consistent is the constructor's responsibility.
This is deliberate. Tagged unions and match-style dispatch would add significant surface area to the language and compiler for an abstraction the composed form already expresses honestly.
See also
- Decorators -
#[align(N)]and the rest of the decorator set - Intrinsics -
$size_ofand$offset_offor inspecting layout - Statements -
if/orchains for reading discriminated values - Types - the type system records and unions build on