Structs
Structs combine named fields into a new type. They are useful when several values form one concept and should travel through a function together.
Declaring a struct
struct Point
< x: I64
< y: I64
The declaration introduces Point. Each indented line declares one field. The leading < exports a field from its module; an unmarked field is private to that module.
Generic parameters follow the type name:
struct Pair A, B
< first: A
< second: B
Pair A, B is one generic struct declaration. Each field uses one of the declared type parameters.
Constructing a value
Write the type name followed by an indented field block:
struct Point
< x: I64
< y: I64
main = !->
origin = Point
x: 0
y: 0
origin.x.println!
origin.y.println!
The field labels are part of the constructor syntax. Rock checks that every required field is present and that each expression has the declared type. Initialize every field explicitly; default field expressions are not reliably preserved by the current semantic pipeline.
Generic fields can often be inferred from their constructor expressions:
struct Pair A, B
< first: A
< second: B
main = !->
entry = Pair
first: "answer"
second: 42
entry.first.println!
entry.second.println!
Here the compiler infers A as &Str and B as I64 from the two field values.
Reading and updating fields
Use dot notation to read a field and to select a field assignment target:
struct Point
< x: I64
< y: I64
main = !->
point = Point
x: 2
y: 3
horizontal = point.x
point.x = 8
horizontal.println!
point.x.println!
horizontal keeps the value read before the update, so the output is 2 followed by 8. Whether a field read copies, borrows, or moves depends on the field type and the surrounding ownership context.
Adding behavior
An inherent impl groups associated functions and methods with a type:
struct Point
< x: I64
< y: I64
impl Point
new: I64 -> I64 -> Point
new = x, y ->
Point
x: x
y: y
@length_squared: I64
@length_squared = -> @x * @x + @y * @y
main = !->
point = Point::new 3, 4
value = point.length_squared!
value.println!
Point::new is an associated function because it is called through the type path. length_squared! is a method because it is called through a value. @ marks a shared receiver, and @x is shorthand for reading the receiver’s field.
Mutable and consuming receiver markers are ^@ and ~@:
struct Counter
< value: I64
impl Counter
^@increment = amount ->
self.value = self.value + amount
return
~@take = -> self.value
main = !->
mut counter = Counter
value: 2
counter.increment 5
total = counter.take!
total.println!
The mut binding is required for increment because its receiver is mutable. take! consumes the value, so counter cannot be used afterward.
Visibility
Export a declaration by placing < before it. Exported fields are independently marked:
< struct PublicPoint
< x: I64
< y: I64
main = !->
point = PublicPoint
x: 1
y: 2
point.x.println!
An explicit export path can also assemble a module facade, but inline exports are easiest to read for a small public type.
Common mistakes
- Omitting a required field or using the wrong field type.
- Treating
Point::newandpoint.newas the same call; the former is associated, the latter is a method-style call. - Forgetting that
@,^@, and~@describe different receiver ownership contracts. - Calling a mutable receiver method without a
mutbinding. - Assuming field defaults are reliable in the current compiler.