Skip to content

Structs

Structs are user-defined composite types with named, typed fields. They are reference types managed by the garbage collector.

Definition

struct Point {
    x: number
    y: number
}

Struct definitions are top-level declarations — they cannot be nested inside functions.

Creating Instances

Use the struct name followed by { field: value } syntax:

var p: Point = Point { x: 10.0, y: 20.0 }

Fields can be provided in any order:

var p: Point = Point { y: 5.0, x: 3.0 }

Field Access

Use dot notation to read fields:

var p: Point = Point { x: 10.0, y: 20.0 }
var dx: number = p.x    # 10.0
var dy: number = p.y    # 20.0

Field Assignment

Use dot notation to modify fields:

var p: Point = Point { x: 1.0, y: 2.0 }
p.x = 99.0
assert(p.x == 99.0)

Structs are reference types — assignment modifies the original, not a copy.

Default Values

Fields can have default values for number, i32, u8, and bool types:

struct Color {
    r: u8 = 0
    g: u8 = 0
    b: u8 = 255
}

var blue: Color = Color {}              # all defaults: r=0, g=0, b=255
var red: Color = Color { r: 255, b: 0 } # g defaults to 0

Only primitive types support defaults. Fields of type string, array, or another struct cannot have default values — the compiler will report an error:

struct Bad {
    name: string = "hello"   # ERROR: default values only for number, i32, u8, bool
    items: array<number>     # no default possible — must be provided
    origin: Point            # no default possible — must be provided
}

Fields without defaults must be provided:

struct Point {
    x: number
    y: number
}

var p: Point = Point { x: 10.0 }  # ERROR: missing field 'y' (no default)

Nested Structs

A struct field can be another struct type:

struct Point {
    x: number
    y: number
}

struct Rect {
    origin: Point
    w: number
    h: number
}

var r: Rect = Rect {
    origin: Point { x: 5.0, y: 10.0 },
    w: 100.0,
    h: 50.0
}

Chained dot access works for nested structs:

var ox: number = r.origin.x     # 5.0
r.origin.x = 42.0               # nested field set
assert(r.origin.x == 42.0)

Structs and Functions

Structs can be passed as function parameters and returned from functions:

struct Point {
    x: number
    y: number
}

fn make_point(x: number, y: number):Point {
    return Point { x: x, y: y }
}

fn distance(p: Point):number {
    return p.x + p.y
}

fn main():void {
    var p: Point = make_point(3.0, 7.0)
    var d: number = distance(p)
    assert(d == 10.0)
}

Type Checking

The compiler enforces field types at compile time:

struct Point {
    x: number
    y: number
}

var p: Point = Point { x: "hello", y: 0.0 }  # ERROR: expected number, got string

Numeric auto-coercion applies to struct fields (same rules as variables):

struct Sensor {
    id: i32
    value: number
}

var s: Sensor = Sensor { id: 42, value: 3.14 }  # 42 auto-converts to i32

Reference Semantics

Structs are GC-managed reference types. Assigning a struct to another variable creates a shared reference, not a copy:

var a: Point = Point { x: 1.0, y: 2.0 }
var b: Point = a
b.x = 99.0
assert(a.x == 99.0)   # both point to the same struct

Equality

Structs use reference equality== compares whether two variables point to the same instance, not whether their fields have the same values:

var a: Point = Point { x: 1.0, y: 2.0 }
var b: Point = a
assert(a == b)   # true — same instance

var c: Point = Point { x: 1.0, y: 2.0 }
assert(a != c)   # true — different instances, even with identical fields

There is no built-in deep equality. To compare field values, compare each field explicitly.

Limits

Limit Value
Max struct definitions 32
Max fields per struct 16

Exceeding either limit produces a compile error. The program will not build.

Bytecode

Struct operations use three opcodes:

Opcode Hex Operand Description
STRUCT_NEW 0xA0 u8 (field count) Pop N field values, create struct, push it
STRUCT_GET 0xA1 u8 (field index) Pop struct, push field value
STRUCT_SET 0xA2 u8 (field index) Pop value + struct, set field, push struct