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
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}")