Chapter 6: Structs and Methods
Defining a struct
struct works the way you'd expect from C, plus the ability to attach methods:
struct Point {
double x;
double y;
double length() const;
void scale(double s);
};Inside the struct body, a method is declared — name, parameters, return type, and whether it's const — but not defined. The definition lives outside, qualified with Type::method:
double Point::length() const {
return self.x * self.x + self.y * self.y;
}
void Point::scale(double s) {
self.x = self.x * s;
self.y = self.y * s;
}self is implicit — you don't declare it as a parameter, but it's available inside every method body, referring to the instance the method was called on. A const method receives self as read-only (it can read fields but not assign to them, enforced the same way any other const binding is); a non-const method like scale can mutate through self.
Calling a method uses the familiar . syntax:
struct Point p = {3.0, 4.0};
double len = p.length(); // 25.0 (this length() returns the squared length)
p.scale(2.0);
// p is now {6.0, 8.0}Under the hood, a method is just an ordinary function with an explicit self parameter prepended — p.length() lowers to something shaped like Point_length(&p), and p.scale(2.0) to Point_scale(&p, 2.0). Nothing magic is happening; it's the same "struct + free functions that take a pointer to it" pattern you'd hand-write in C, with syntax sugar on top.
Operator overloading
A struct can define what +, -, *, /, %, and the comparison operators mean for its own type, by naming a method operator+ (etc.):
struct Vec2 {
double x;
double y;
Vec2 operator+(Vec2 other) const;
};
Vec2 Vec2::operator+(Vec2 other) const {
Vec2 result;
result.x = self.x + other.x;
result.y = self.y + other.y;
return result;
}struct Vec2 a = {1.0, 2.0};
struct Vec2 b = {3.0, 4.0};
struct Vec2 c = a + b; // {4.0, 6.0} -- calls Vec2::operator+This is exactly the kind of thing that makes small numeric/geometric types (2D/3D vectors, complex numbers, fixed-point values) pleasant to work with — write the arithmetic once as a method, then use ordinary operator syntax everywhere else instead of vec2_add(a, b) calls scattered through the codebase.
Structs and regions
One thing worth connecting back to the previous chapter: a struct's fields don't have their own independent regions — the whole struct lives in whatever region the variable holding it lives in. A struct Point p declared as a local variable is &stack-region data as a whole; a &arena<Pool> struct Point allocated via new<Pool> lives in the arena, fields included. There's no scenario where one field of a struct is secretly heap-allocated while the rest sits on the stack unless you make that explicit yourself (e.g., a field that's itself a &heap T reference, which then has its own independent lifetime worth tracking separately from the struct it's embedded in).
Next: Enums, Unions, and Match — structs group related data together; enums and unions express alternative shapes of data, and match is how you handle every alternative safely.