Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.