Skip to content

Structs

Declare nominal product types with fields, constructors, methods, generics, and recursion.

Updated View as Markdown

A struct is a named product type.

struct Point {
    x: float
    y: float
}

Struct names use PascalCase; field names use snake_case.

Construction

Construct a value by naming its fields.

origin := Point { x: 0.0, y: 0.0 }

Named fields keep construction independent of declaration order. Shorthand uses a visible binding with the same name.

x := 10.0
y := 20.0
point := Point { x, y }

Values supplied for known fields are checked against their declared types.

Current boundary

The parser accepts field defaults, but the current semantic constructor path does not apply them. It also does not yet diagnose every missing, duplicate, or unknown field in a struct literal. An omitted field can therefore reach lowering without an initialized source value.

Until construction checking is completed, initialize every declared field exactly once and do not rely on a declaration default:

struct Retry {
    attempts: int = 3
    enabled: bool
}

retry := Retry {
    attempts: 3,
    enabled: true,
}

Treat an incomplete literal as unsupported even if the current compiler accepts it. Put reusable defaults and validation in a named constructor, and have that constructor explicitly initialize every field.

Access

Use . to read a field.

distance := point.x * point.x + point.y * point.y

Assigning a field requires a mutable path to the value.

mut point := Point { x: 0.0, y: 0.0 }
point.x = 4.0

The field declaration itself does not need a mut modifier. Mutability comes from the binding and receiver path.

Nested assignment requires the complete path to be mutable. A mutable outer binding does not bypass a borrowed receiver’s contract, and assigning one field does not reconstruct unrelated fields.

Methods

Methods can be declared in the struct body.

struct Counter {
    value: int

    fn current(self): int => self.value

    fn increment(mut self): void {
        self.value += 1
    }
}

self is a read receiver; mut self permits mutation. A function without self is static.

struct Point {
    x: float
    y: float

    fn zero(): Point => Point { x: 0.0, y: 0.0 }
}

Methods can also be supplied by an inherent impl block or a receiver-prefix function. Trait implementations are covered in Implementations.

Receiver form is part of the method contract:

Receiver Capability
no self static function called through the type
self read the value and call non-mutating behavior
mut self update fields through the receiver

Call syntax remains value.method() or Type.function() regardless of where the implementation is written.

Generics

struct Page[T] {
    items: []T
    next_cursor: string?
}

Every concrete Page[T] has fields checked with its substituted T. Generic arguments use square brackets.

Generic inference can obtain T from constructor fields or an expected type. An empty collection field may still need an annotation when no value constrains its element type.

Decorators

Struct and field decorators can add compiler or subsystem metadata.

@derive(Equatable)
struct User {
    id: int

    @weak
    manager: User?
}

@derive, @externalizable, @weak, and model-specific decorators have different target restrictions. See Decorators.

Recursion

A recursive value must include a representational break.

struct Node {
    value: int
    children: []Node
}

Managed containers and supported optional/boxed layouts can provide indirection. A struct that embeds itself directly at full extent has no finite size and is rejected.

Mutually recursive declarations are checked by the same finite-layout rule.

Identity

Structs are nominal: two declarations with identical fields remain different types.

struct Width { value: int }
struct Height { value: int }

The values do not interchange merely because both contain one int.

Storage representation is compiler-selected. Source equality follows Equatable, not storage-handle identity.

Nominality is module-aware. Two modules can declare the same type name and field list without creating one shared type; imports retain the defining module’s identity.

Spelling

The current declaration is struct Name { ... }. Legacy type Name { ... } and data Name { ... } forms are not current struct syntax. type declares an alias.

Use an anonymous record for a short-lived structural shape, and a model only when the fields describe a persisted database row.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close