Skip to content

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.

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. self is an implicit first parameter — you do not list it.

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.
p.x # read
p.x = 5 # write

Accessing 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).

Call a method with the usual .method(args) syntax:

Point q = p.translate(3, 4)
float d = p.distance()
  • self is the implicit first parameter, is read-only (self = other is a compile error), and refers directly to the receiver — the receiver is not copied on a method call, so self.x = e is visible to the caller.
  • return self goes 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 Point and Color can 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.

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).

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.

Two structs of the same type compare with == / != field by field (first level only). Comparing different struct types is a compile error.

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 unchanged

The base’s type must match the struct being constructed.

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?.x
string? c = p?.addr?.city # nested chaining

Using ?. on a non-nullable receiver is a compile error (use .).