Structs
A struct is a user-defined record type. Unlike array<T> and map<K,V>, which are
shared reference values, a struct has value semantics: assignment copies it.
Definition
Section titled “Definition”struct Point { int x int y
def distance() float { return to_float(self.x * self.x + self.y * self.y) }
def translate(int dx, int dy) Point { return Point { x: self.x + dx, y: self.y + dy } }}- Fields are written
type name(the same order as a variable declaration) with no initializers. They may be separated by commas or newlines. - Methods are
defs written inside the struct block.selfis an implicit first parameter — you do not list it.
Construction
Section titled “Construction”Instances are created with Name { field: value, ... }:
Point p = Point { x: 1, y: 2 }- Every field must be specified. Fields may be given in any order.
- A missing field, an extra field, a non-existent field, a type mismatch, or specifying the same field twice is a compile error.
Field access
Section titled “Field access”p.x # readp.x = 5 # writeAccessing a non-existent field is a compile error. Field access does not copy, so
writing through a nested struct works naturally (outer.inner.x = 3).
Methods and self
Section titled “Methods and self”Call a method with the usual .method(args) syntax:
Point q = p.translate(3, 4)float d = p.distance()selfis the implicit first parameter, is read-only (self = otheris a compile error), and refers directly to the receiver — the receiver is not copied on a method call, soself.x = eis visible to the caller.return selfgoes through the normal return path and is copied, so the caller receives an independent value (consistent with value semantics).- Methods are not registered in a global function table — they are reachable only through a
receiver, so
PointandColorcan each have a method of the same name with no clash. - A field and a method may not share a name. Calling a property with
()or forgetting the()on a method are both compile errors.
Value semantics
Section titled “Value semantics”auto p2 = p copies all fields into a new struct; changes to p2 do not affect p. This
mirrors Go, Rust, and Swift structs, and is the deliberate opposite of the reference
semantics of array and map (collections are shared; records are independent). A copy is
made at each of these points:
- declaration and assignment (
Point p = ...,p = ...) - passing a struct as a function argument (
f(p)) - returning a struct (
return p) - initializing or writing a struct-typed field
Field access does not copy (so nested writes work).
Nominal typing
Section titled “Nominal typing”Point and Color are distinct types even if their fields are identical — Pluvial
identifies structs by name, not by shape (like Go and Rust, unlike TypeScript). This is
consistent with the “do nothing implicitly” principle: similar shapes are not silently
treated as the same type.
Equality
Section titled “Equality”Two structs of the same type compare with == / != field by field (first level only).
Comparing different struct types is a compile error.
Struct update syntax
Section titled “Struct update syntax”A new struct can be built from an existing one with a spread base, overriding selected fields. The base must come first, and the original is left unchanged (immutable update):
Point p = Point { x: 1, y: 2 }Point p2 = Point { ...p, x: 5 } # { x: 5, y: 2 }, p unchangedThe base’s type must match the struct being constructed.
Optional chaining
Section titled “Optional chaining”For a nullable struct receiver, ?. accesses a field or calls a method, short-circuiting to
null if the receiver is null. The result type is always T?, and chaining propagates:
Point? p = find_point()int? x = p?.xstring? c = p?.addr?.city # nested chainingUsing ?. on a non-nullable receiver is a compile error (use .).