Language Policy

Records & Nominal Data

Canonical grammar, semantics, constraints, and deferred boundaries for Records & Nominal Data.

Topaz separates structural records from nominal records, enums, and newtypes so field shape, declaration identity, construction, update, matching, comparison, and protocol conformance cannot be confused.

Policy

A structural record literal has one or more field: expression entries; value{ field: replacement } makes a shallow structural update. record Name, enum Name, and newtype Name = Base are top-level nominal declarations with invariant parameters. Nominal construction uses the bare declaration name. Record defaults run once per construction in declaration order after explicit fields; enum constructors live under the enum namespace; newtype .value() unwraps one layer.

Specified behavior

Structural equality compares compatible field shapes independent of source order. Nominal equality first requires the same declaration identity, then follows variant/payload or declaration field order. Nominal spread allows exactly one leading ...source in Name { ...source, replacements }; it creates a fresh shallow outer value, evaluates replacements left to right, and does not rerun defaults. Nominal record patterns require a nonempty selected field subset and test identity before fields.

Constraints and loud decline

JavaScript { ...value }, multiple or non-leading nominal spreads, namespace-qualified brace construction, implicit this, &self, &mut self, receiver declarations inside record bodies, empty nominal patterns, duplicate or unknown fields, and construction through a shadowed name are rejected. Structural update is not a nominal update, and a nominal value is not accepted merely because a structural shape matches.

Deferred boundary

Generic derives, broad imported-target implementations, dynamic or first-class protocol dispatch, qualified construction, open nominal patterns, reflection, inheritance, and row polymorphism are deferred. Receiver methods exist only in bounded module-top-level impl Name blocks with bare immutable by-value self; they do not import Rust ownership syntax.

Worked example

TOPAZ
record User {
    name: string,
    age: int = 0,
}

let before: User = User { name: "Ada", age: 36 }
let after: User = User { ...before, age: 37 }
print("{before.age}:{after.age}")