← Back to AILP Home

Struct

Overview

Structs are composite types that group named fields. Methods are defined via trait implementations.

Declaration

struct:Point = {
    int32:x;
    int32:y;
};

struct:Person = {
    string:name;
    int32:age;
};

Instantiation and Access

Struct literal syntax with named fields:

stack Point:p = Point{x: 10, y: 20};
println(`&{p.x}`);    // 10
println(`&{p.y}`);    // 20

stack Person:alice = Person{name: "Alice", age: 30};

Positional arguments (order must match declaration):

Point:p = Point{10i32, 20i32};

Constructor syntax (desugars to Type_create(args)):

Counter:c = raw instance<Counter>(10i32);

Fields can also be assigned after declaration:

Point:p;
p.x = 10i32;
p.y = 20i32;

Methods (via Trait Impls)

Methods are defined through trait implementations:

trait:HasArea = {
    func:area: int32(Rect2D:self);
};

struct:Rect2D = {
    int32:w;
    int32:h;
};

impl:HasArea:for:Rect2D = {
    func:area = int32(Rect2D:self) {
        pass self.w * self.h;
    };
};

// Call via UFCS (Uniform Function Call Syntax)
Rect2D:rect;
rect.w = 10i32;
rect.h = 5i32;
Result<int32>:a = rect.area();

Passing Structs

func:add_coords = int32(Point:p) {
    pass p.x + p.y;
};

Point:origin = Point{ x: 0, y: 0 };
int32:sum = raw add_coords(origin);

Related

Struct Update Syntax (v0.19.1)

Create a copy of a struct with selected fields overridden:

struct:Point2D = {
    int32:x;
    int32:y;
};

Point2D:origin = Point2D{ x: 0i32, y: 0i32 };
Point2D:shifted = Point2D{ ...origin, x: 5i32 };
// shifted.x = 5, shifted.y = 0 (copied from origin)

Rules: - The base variable (...base) must be the same struct type as the target. - All named field overrides are applied after copying the base. - The base is unchanged (value copy, not mutation). - The base must be a plain owned variable, not a $$m borrow alias. - Unknown field names in overrides are a compile-time error.

Extern Functions Returning Structs (v0.19.1)

Prior to v0.19.1, returning a struct value from an extern call required storing to a temporary variable first. This restriction is lifted:

// v0.19.1+: direct pass of extern-returned struct value works correctly
extern func:get_point = Point();

func:example = int32() {
    pass raw get_point().x;   // direct field access on extern return
};

Struct Equality (v0.35.4)

Struct values can be compared with == when both operands are the same struct type. Equality expands to per-field comparisons:

struct:Point = { int32:x; int32:y; };

Point:a = Point{ x: 3, y: 4 };
Point:b = Point{ x: 3, y: 4 };
Point:c = Point{ x: 0, y: 0 };

if (a == b) { /* true — all fields match */ };
if (a == c) { /* false */ };

Comparing two incompatible struct types is a compile-time type error. Struct == does not imply deep equality of heap-allocated fields (pointers compare by address, not by referent content).

See also docs/abi.md for the struct FFI ABI table (by-value vs by-pointer thresholds, padding and alignment rules).

dyn Trait struct fields (v0.35.5)

A struct field may be declared as dyn TraitName, making that field a fat pointer ({data*, vtable*}) that can hold any concrete type for which the trait is implemented:

trait:Drawable = {
    func:draw = int32(Drawable:self);
};

struct:Circle = { int32:radius; };
struct:Widget = { dyn Drawable:inner; };

impl:Drawable:for:Circle = {
    func:draw = int32(Circle:self) { pass self.radius; };
};

func:main = int32() {
    Circle:c = Circle{ radius: 42 };
    Widget:w;
    w.inner = c;              // concrete Circle → dyn Drawable coercion
    int32:r = w.inner.draw(); // dynamic dispatch through vtable
    exit raw r;               // 42
};

The fat pointer occupies 16 bytes in the struct layout regardless of the concrete type. Field-level borrows through dyn are not yet supported — borrow the whole struct instead. If no impl of the trait exists in scope, the assignment emits ARIA-043.

See traits/dyn.md for the full dyn Trait reference.