A struct is a named record of fields, with methods defined inside it. Structs are
values like everything else in Brine: copying one copies its fields, == compares
fields, and print shows them. A method that changes its struct takes ref self.
Defining and constructing
Section titled “Defining and constructing”struct Point: x: int y: int
struct Line: start: Point end: Point label: str
fn main(): let a = Point(x=1, y=2) # keyword: any order let b = Point(3, 4) # positional: declaration order var line = Line(label="diagonal", start=a, end=b) line.end.y = 40 # nested fields print(a.x + b.y) print(line) print(f"from {line.start} to {line.end}") print(a == Point(1, 2), a != b)5Line(start=Point(x=1, y=2), end=Point(x=3, y=40), label="diagonal")from Point(x=1, y=2) to Point(x=3, y=40)true true- Each field has a name and a type. Fields have no default values.
- Construct with all fields positionally, in declaration order (
Point(3, 4)), or all by keyword, in any order (Point(y=4, x=3)). Don’t mix the two. - Read and assign fields with a dot. Assigning needs a
var; aletstruct can’t change at all.
Every field needs a value:
struct Point: x: int y: int
fn main(): let p = Point(x=1) print(p)error[E0302]: missing field `y` in `Point(...)` --> main.br:6:13 |6 | let p = Point(x=1) | ^^^^^^^^^^ every field needs a value = run `brine explain E0302` for more about this errorerror: could not compile due to 1 errorMethods
Section titled “Methods”Methods are functions declared inside the struct. The first parameter decides what a method may do:
self: the method reads its receiver (a read-only copy, like any parameter).ref self: the method can change its receiver. It can only be called on something you could pass asref: avar, or an element or field of one.- no
self: a static method, called on the type:Particle.at_rest(1.0). Use these for constructors.
struct Particle: pos: float vel: float
fn energy(self) -> float: # reads its receiver return 0.5 * self.vel * self.vel
fn step(ref self, dt: float): # changes its receiver self.pos += self.vel * dt
fn at_rest(pos: float) -> Particle: # no self: a static method return Particle(pos=pos, vel=0.0)
fn main(): var p = Particle(pos=0.0, vel=2.0) p.step(0.5) p.step(0.5) print(p, p.energy()) let q = Particle.at_rest(1.0) print(q) var ps = [Particle(0.0, 1.0), Particle(5.0, -1.0)] for i in 0..len(ps): ps[i].step(1.0) # on an array element print(ps)Particle(pos=2.0, vel=2.0) 2.0Particle(pos=1.0, vel=0.0)[Particle(pos=1.0, vel=1.0), Particle(pos=4.0, vel=-1.0)]The call p.step(0.5) doesn’t need a ref at the call site: the method’s name already
says what it does, and the compiler checks that p can be changed.
struct Counter: n: int
fn bump(ref self): self.n += 1
fn main(): let c = Counter(n=0) c.bump()error[E0401]: cannot call `ref self` method `bump` on `c` because it was declared with `let` --> main.br:9:5 |9 | c.bump() | ^ immutable8 | let c = Counter(n=0) | - declared here | = help: declare it with `var c = ...` to make it mutable = run `brine explain E0401` for more about this errorerror: could not compile due to 1 errorIn a plain self method, self is read-only, and assigning to one of its fields is
E0403. mut self isn’t allowed: it would only change a
copy. To return a changed copy, build a new value:
return Particle(pos=self.pos + 1.0, vel=self.vel).
Methods may have type parameters of their own; see generics.
Printing and equality
Section titled “Printing and equality”print, str(...) and f-strings show a struct as its name and its fields, nested
values included: Point(x=1, y=2). Strings inside are quoted (label="diagonal").
== and != compare two structs of the same type field by field. Structs can’t be
ordered: < on two Points is E0305. A struct whose
fields are all valid map keys (integers, bool, str, enums, and arrays, tuples and
structs of those) can be a map key too.
Value semantics
Section titled “Value semantics”A struct is a value. var q = p copies p, and passing a struct to a function passes a
copy, so changing one never changes the other. (The copy is cheap: arrays and strings
inside are copy-on-write.)
struct Point: x: int y: int
fn moved(p: Point, dx: int) -> Point: var q = p # a copy q.x += dx return q
fn main(): let a = Point(1, 2) let b = moved(a, 10) var c = a c.y = 0 print(a, b, c)Point(x=1, y=2) Point(x=11, y=2) Point(x=1, y=0)To let a function change a struct the caller owns, use a ref self method or a ref
parameter (see functions).
Recursive structs
Section titled “Recursive structs”A struct can’t contain itself directly, because it would have infinite size. Put the
recursive part in an array (children: [Node]) or use a recursive
enum.
struct Node: value: int next: Node
fn main(): print(1)error[E0309]: struct `Node` contains itself and would have infinite size --> main.br:1:1 |1 | struct Node: | ^^^^^^ recursive struct | = help: store the recursive part inside an array or an enum, e.g. `children: [Node]` = run `brine explain E0309` for more about this errorerror: could not compile due to 1 errorSummary
Section titled “Summary”struct Name:followed byfield: Typelines andfnmethods.- Construct positionally (
Point(1, 2)) or by keyword (Point(x=1, y=2)), not mixed; every field needs a value. selfmethods read,ref selfmethods change the receiver (which must be avar), methods withoutselfare static (Type.method()).- Structs print as
Name(field=value, ...)and compare with==field by field. - Copies are independent. Generic structs: see generics.