structs & refs

Methods & Constructors

Methods mutate in place, readonly methods work on a copy, operators overload C++-style, and constructors are called without new.

Methods and operators

Instance methods take this as an implicit refm.points[i].normalize() mutates in place, exactly what it looks like. A readonly method takes this as a flattened copy instead, which is why it is the only kind callable on a temporary: make(3, 4).sum() consumes the leaves already on the stack. Writing this inside a readonly method is a compile error, as is calling a mutating method on a temporary or through readonly this. Operators are C++-style members — operator+(a, b) — taking (left, right) by value, dispatched on the left operand’s struct; a one-argument operator- is unary negation. != always derives as the negation of == and cannot be declared. Reference classes use the same member syntax — instance forms dispatch on this (operator[](i), operator+(other)), static forms take (left, right), and unchecked operator[] declares the unchecked() fast path. There is no @operator decorator (upstream’s spelling); classes may declare operator!= because reference inequality is otherwise the default.

methods.as
export struct Vec {
    x: f32 = 0;
    y: f32 = 0;
    scale(s: f32): void {          // this is an implicit ref
        this.x *= s;
        this.y *= s;
    }
    readonly sum(): f32 {          // this is a copy; rvalue receivers work
        return this.x + this.y;
    }
    operator+(a: Vec, b: Vec): Vec {
        let r = Vec();
        r.x = a.x + b.x;
        r.y = a.y + b.y;
        return r;
    }
    operator-(v: Vec): Vec {       // one argument: unary negation
        let r = Vec();
        r.x = -v.x;
        r.y = -v.y;
        return r;
    }
}

export function demo(): f32 {
    let v = Vec();
    v.x = 3;
    v.scale(2);                    // value-local receiver: spills, mutates v
    let w = v + -v;                // operator dispatch, by value
    return w.sum() + (v + v).sum(); // 0 + 12
}

Constructors

Structs declare C++-style constructors: overloadable, by value, called without new. A constructor body starts from the default-initialized value (field initializers applied), mutates this, and returns it implicitly — bare return exits early, returning a value is an error. Overloads resolve by arity first, then exact argument types, then numeric coercibility; anything still plural is an ambiguity error at the call site.

ctors.as
export struct Color {
    r: f32 = 0;
    g: f32 = 0;
    b: f32 = 0;
    a: f32 = 1;                      // defaults run before every body
    constructor(r: f32, g: f32, b: f32) {
        this.r = r;
        this.g = g;
        this.b = b;
    }
    constructor(gray: f32) {
        this.r = gray;
        this.g = gray;
        this.b = gray;
    }
}

export function demo(): f32 {
    let c = Color(0.5);              // no new: construction is by value
    let d = Color(1, 0, 0);
    return c.g + d.r + d.a;          // 0.5 + 1 + 1
}

new does not apply to structs at all — construction is the call form, and V() spells the default value whether or not constructors are declared. Declaring constructors never suppresses the default (a deliberate divergence from C++): array elements and uninitialized locals need the default state to exist.