Structs

Custom data types that group related fields together.

Overview

Syntax Description
struct Name { fields } Define a struct
pub struct Name { fields } Define a public struct (visible to other modules)
struct Name<T> { fields } Define a generic struct
Name { field = value } Create an instance
@field Read a field
value struct.field Write a field
fn (s:Name) method(...) Define a method

Defining structs

struct Point {
    x:f64
    y:f64
}

Field types can be any valid type: i64, f64, str, ptr, another struct, or a pointer to a struct (*StructName).

Default values

Fields can have default values:

struct Config {
    debug:i64 = 0
    verbose:i64 = 0
    max_retries:i64 = 3
}

fn main() {
    Config {} -> cfg           // all defaults
    cfg @max_retries print nl  // 3
}

Public structs

Use pub to export a struct from a module:

pub struct Vec2 {
    x:f64
    y:f64
}

Creating instances

Push field values with field = expr inside braces:

Point { x = 3.0 y = 4.0 } -> p

All fields without defaults must be provided. Order does not matter.


Field access (read)

Use @fieldname to read a field value. The struct is consumed from the stack and the field value is pushed.

Syntax: struct @field → pushes field value

p @x print nl  // read x from p

Chained access

For nested structs, chain @ operators:

struct Line {
    start:Point
    end:Point
}

line @start @x print nl  // read x from start point of line

Field set (write)

Use .fieldname to write a field value. The value to set must be on the stack before the struct.

Syntax: value struct.field

5.0 p.x       // set p.x to 5.0
10.0 p.y      // set p.y to 10.0

Methods

Methods are functions with a receiver parameter. The receiver uses a special syntax before the function name.

Syntax: fn (receiver:StructType) name(params -- returns) { body }

struct Circle {
    radius:f64
}

fn (c:Circle) area( -- a:f64) {
    c @radius dup * 3.14159 *
}

fn main() {
    Circle { radius = 5.0 } -> c
    c area print nl  // 78.5397
}

Method calls

When a struct is on the stack and you call a function name, the compiler checks for a matching method on that struct type first:

c area       // calls (c:Circle) area

The struct is consumed from the stack as the receiver.

Methods with parameters

fn (p:Point) translate(dx:f64 dy:f64 -- ) {
    -> dy -> dx
    p @x dx + p .x
    p @y dy + p .y
}

fn main() {
    Point { x = 1.0 y = 2.0 } -> p
    p 3.0 4.0 translate
    p @x print nl  // 4
    p @y print nl  // 6
}

Method resolution

Methods take precedence over global functions with the same name:

fn (c:Counter) bump( -- result:i64) {
    c @value 1 +
}

fn bump( -- result:i64) {
    999
}

fn main() {
    Counter { value = 10 } -> c
    c bump print nl  // 11 (method)
    bump print nl    // 999 (global)
}

Generic structs

Structs can have type parameters:

struct Box<T> {
    value:T
}

fn main() {
    Box<i64> { value = 42 } -> b
    b @value print nl  // 42
}

Multiple type parameters:

struct Pair<A, B> {
    first:A
    second:B
}

Note: Generic structs currently work best with i64 fields. Using f64 or str type parameters may return raw bits or pointers.


See also