Types
Data Types
All user-defined types use the data keyword. data is a sum type — a single-variant data acts like a struct, a multi-variant data acts like an enum. The compiler optimizes single-variant types (no tag needed).
Single-Variant (Struct)
Single-variant data is constructed with a headed aggregate literal:
Point :: data .{ .x: i32, .y: i32 }
p := Point.{ .x = 1, .y = 2 }
p.x // 1
p.y // 2
Multi-Variant (Enum)
Indented variants:
Shape :: data
Square .{ f32 }
Circle .{ f32 }
area :: fn(s: Shape) -> f32
match s
Shape::Square.{ side } => side * side
Shape::Circle.{ r } => 3.14 * r * r
Simple Enums (No Fields)
Pipe syntax for variants without fields:
Color :: data
Red
Green
Blue
Or indented:
Color :: data
Red
Green
Blue
Variant Field Shapes
Each variant independently chooses its field shape:
// No fields
Unit :: data
// Positional product fields
Pair :: data .{ i32, i32 }
// Named fields
Person :: data .{ .name: []const u8, .age: i32 }
// Mixed per variant
Expr :: data
Lit .{ i32 }
Add .{ .lhs: i32, .rhs: i32 }
Nil
Explicit Variant Name
For single-variant types, you can write the variant name explicitly. If the variant name matches the type name, it’s treated as a single-variant type:
Person :: data
Person .{ .name: []const u8, .age: i32 }
// Equivalent to:
Person :: data .{ .name: []const u8, .age: i32 }
UFCS (Uniform Function Call Syntax)
Any free function whose first parameter matches a type can be called with dot syntax:
Person :: data .{ .name: i32, .age: i32 }
greet :: fn(p: Person) -> i32
p.age + 1
p := Person.{ .name = 10, .age = 30 }
p.greet() // calls greet(p)
This works on any type, including primitives:
double :: fn(x: i32) -> i32
x * 2
5.double() // 10
Type Aliases
type Id = i32
type Callback = fn(i32) -> i32
Aliases are transparent — the compiler treats them as the underlying type.
Error Sets
data FileError = error
FileNotFound
ReadFailed
Error sets define named error codes used with error unions (!T).
Error Unions
A function that can fail returns !T (error union):
fn read_file(path: []const u8) -> !i32
let fd = open(path.ptr, 0)
return error.FileNotFound if fd < 0
// ...
size
The caller uses try to propagate errors:
let size = try read_file("test.txt")
Or catch to handle them:
let size = read_file("test.txt") catch e
0 // default on error
Optionals
?T is an optional type — either a value of type T or nil:
fn find(items: []i32, target: i32) -> ?i32
// ...
return nil // not found
Use orelse to provide a default:
let x = find(items, 42) orelse -1
Primitive Types
See Literals & Primitive Types for the full list of built-in types:
i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool, isize, usize.
Pointer Types
See Pointers for *T, *const T, [*]T, and [*]const T.
Slice Types
See Slices & Strings for []T and []const T.