Section 7.4

The constructor

When building is more than copying fields — code that runs at birth.

Construction by named fields copies values, and that is all. When being born takes more — a derived value, a default that is not zero, a rule to enforce — the archetype declares a constructor.

archetype Potion {
    name: str;
    doses: int;

    constructor(name: str) {
        self.name := name;
        self.doses := 3;
    }

    drink() {
        if (self.doses = 0) {
            Console.print(text: $"{self.name}: the flask is empty.");
            return;
        }
        self.doses := self.doses - 1;
        Console.print(text: $"{self.name}: one dose down, {self.doses} left.");
    }
}

@entry(start)
object MyProgram {
    start() {
        var vigour := Potion(name: "vigour");

        vigour.drink();
        vigour.drink();
        vigour.drink();
        vigour.drink();
    }
}

Code at birth

constructor(name: str) {
    self.name := name;
    self.doses := 3;
}

constructor is the only member of an archetype that carries a keyword. It runs the moment Potion(name: "vigour") builds the instance: here it stores the name it was given and decides that every potion is born with three doses — a default that is not the zero of omitted fields.

Its parameters are construction’s new façade: the call gives what the constructor asks for, and the constructor decides what the fields become.

The constructor replaces construction by fields

As soon as an archetype has a constructor, everything is built through it. Try Potion(name: "vigour", doses: 9):

error[T022]: 'Potion' has no parameter 'doses'

Fields are no longer parameters — only the constructor’s are. That is the point of the mechanism: if Potion guarantees three doses at birth, nobody can build a nine-dose one through the side door.

State keeps its rules

drink() refuses to go below zero: the empty flask says so, and nothing breaks. Constructor and methods are the two ends of one discipline — one guarantees how an instance is born, the others how it evolves.

Your turn

  • Add a doses: int parameter to the constructor and pass it to self.doses: the call Potion(name: "vigour", doses: 5) becomes legal again — because the constructor accepts it.
  • Have the potion born with self.doses := 0;: the first drink() hits the empty flask.
  • Give the parameter a default — constructor(name: str, doses: int := 3) — and get the suppleness of 5.4 back: Potion(name: "vigour") and Potion(name: "heal", doses: 5) both work.