Structs

A struct is a user-defined data type used to group variables into a single type. Additionally, structs may define methods (member functions), that can be called on instances of the struct. Inside methods, this is a non-null reference (T&) to the instance that the function was called on. Use &this when a storable T* is required. Methods must be defined inside the struct or enum body.

struct Person {
    string name;
    int age;
    Person*? friend;

    void introduce() {
        // Members may be accessed via 'this' or directly.
        println(this.name, " is ", age, " years old");
    }
}

void main() {
    var alice = Person(name = "Alice", age = 30, friend = null);
    var bob = Person(name = "Bob", age = 25, friend = &alice);
    alice.friend = &bob;

    alice.introduce(); // prints "Alice is 30 years old"
    bob.introduce(); // prints "Bob is 25 years old"
}

Constructors

A struct without an explicit constructor gets an autogenerated one that takes each field as a parameter, positionally or by name:

struct Point {
    int x;
    int y;
}

void main() {
    var a = Point(1, 2);
    var b = Point(x = 3, y = 4);
    println(a.x + b.x); // prints 4
}

Named arguments can be given in any order and can skip fields with default values. Argument expressions always evaluate in the order written.

To customize construction, declare a constructor: a method with the same name as the struct. It must initialize every field without a default value; fields with defaults are initialized automatically unless the constructor plainly assigns them before reading them. Defaults evaluate in field order before the body runs.

Field default scope

Like parameter defaults, they resolve where they are declared: names in a default never capture locals at the construction site.

Use this. to refer to fields shadowed by parameters. A constructor can delegate to another with a top-level init(...) call;

Constructor delegation edge cases

The target performs initialization, so the delegating body needs no assignments of its own. Calling init(...) on another instance instead reinitializes that instance in place. Overwriting a delegation-built field after the init(...) call destroys the old value first. Storing to a field before the init(...) call is an error when the delegation provably overwrites it: such stores can never be observed.

Constructors are never implicit: a single-parameter constructor does not convert its argument type, so pass Wrapper(5) explicitly instead of a bare 5:

struct Counter {
    int count;

    Counter(int count) {
        this.count = count;
    }
}

void main() {
    var counter = Counter(10);
    println(counter.count); // prints 10
}

A constructor marked implicit also acts as a conversion from its argument type; see Implicit conversions.

See Move semantics for how values are copied and moved.

Printing

Structs print generically: print renders the struct name followed by each field, so no boilerplate is needed for logging and debugging. Calling .toString() works too: both are provided automatically, with no : Printable declaration needed.

struct Point {
    int x;
    int y;
}

void main() {
    println(Point(1, 2)); // prints "Point(x: 1, y: 2)"
}

Nested structs, generic structs instantiated with printable arguments, and containers of structs all work. Nullable fields print as null when null.

Custom print methods

A custom print method overrides the compiler-generated one. A hand-written print alone does not make print(value) work: like before, the struct must still declare : Printable for that.