Struct
A tuple groups values by position. A struct groups them by name, and gives the group a type of its own. A Point is not just any two numbers: its x cannot be mistaken for its y the way .0 can be mistaken for .1, and a function that asks for a Point cannot be handed a (int, int) that happens to mean something else.
This lesson is only about the data — declaring the fields, building a value, reading and writing the fields. Functions that belong to a struct come in Method, a few lessons on.
Declaring a struct
A declaration is struct, a name, and a list of fields in braces. Each field has a name, a colon, a type and a closing semicolon:
struct Point {
x: int;
y: int;
}
A field may have any type, including another struct:
struct Box {
corner: Point;
width: int;
height: int;
}
The declarations sit at the top level of the file, beside the functions, and from then on Point and Box are types like int or bool.
A tuple (int, int) | A struct Point |
|---|---|
Fields are read by position: .0, .1 | Fields are read by name: .x, .y |
Any two ints have the same type | A Point is its own type, with its name |
| Written on the spot, no declaration | Declared once, then used everywhere |
| Good for a quick pair from a function | Good for data that means something on its own |
Building a value
A struct literal is the type's name and a value for every field, in braces:
let origin = Point { x: 0, y: 0 };
let corner = Point { y: 2, x: 5 };
Because every value is labelled, the fields may come in any order — corner lists y first. None may be left out, though: a struct value always has all of its fields, so Point { x: 1 } is refused.
A field holding a struct takes a struct value, and is read by chaining the dots:
let box = Box { corner: corner, width: 4, height: 3 };
PrintLine("box at ({}, {}), {} by {}", box.corner.x, box.corner.y, box.width, box.height);
Changing a field
A field is assigned like a variable, and the same rule applies: the binding must be a var. Under let the whole struct is read-only, every field included.
var cursor = Point { x: 1, y: 1 };
cursor.x = 10;
cursor.y += 5;
A struct is a value
A struct can be passed to a function and returned from one, like any other value:
func Shifted(point: Point, dx: int, dy: int) -> Point {
return Point { x: point.x + dx, y: point.y + dy };
}
Shifted is handed a copy of the point it is called with. It builds a new Point and returns that, so the caller's cursor comes out exactly as it went in:
flowchart LR
cursor["cursor<br/>(10, 6)"] -- "copied in" --> point["point<br/>(10, 6)"]
point -- "builds" --> result["Point (7, 10)"]
result -- "returned" --> moved["moved<br/>(7, 10)"]
cursor -. "unchanged" .-> after["cursor<br/>(10, 6)"]For two numbers the copy costs nothing. For a large struct it does, and for a function that should change the caller's value a copy is no use at all — the next two lessons, Reference and Mutable reference, deal with both.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A tuple groups values by position. A struct groups them by name, and gives the group a type of
// its own: a `Point` is not just any two numbers, and its `x` cannot be mistaken for its `y` the
// way `.0` can be mistaken for `.1`.
//
// This lesson is only about the data: declaring the fields, building a value with a struct
// literal, and reading and writing the fields. Functions that belong to a struct come a few
// lessons later.
import Io::PrintLine;
// A declaration lists the fields, each with its type and a closing semicolon.
struct Point {
x: int;
y: int;
}
// A field may have any type, including another struct.
struct Box {
corner: Point;
width: int;
height: int;
}
// A struct is a type like any other, so a function can take one and give one back.
func Shifted(point: Point, dx: int, dy: int) -> Point {
return Point { x: point.x + dx, y: point.y + dy };
}
func Main() -> int {
// A struct literal is the type's name and a value for every field. The fields may come in
// any order, but none may be left out: `Point { x: 1 }` is rejected for missing `y`.
let origin = Point { x: 0, y: 0 };
let corner = Point { y: 2, x: 5 };
PrintLine("origin ({}, {})", origin.x, origin.y);
PrintLine("corner ({}, {})", corner.x, corner.y);
// A field holding a struct is read by chaining the dots.
let box = Box { corner: corner, width: 4, height: 3 };
PrintLine("box at ({}, {}), {} by {}", box.corner.x, box.corner.y, box.width, box.height);
// A field is assigned like a variable, and the same rule applies: the binding must be a
// `var`. Under `let` the whole struct is read-only, every field included.
var cursor = Point { x: 1, y: 1 };
cursor.x = 10;
cursor.y += 5;
PrintLine("cursor ({}, {})", cursor.x, cursor.y);
// `Shifted` was handed a copy of `cursor` and built a new point, so `cursor` is unchanged.
let moved = Shifted(cursor, -3, 4);
PrintLine("moved ({}, {})", moved.x, moved.y);
PrintLine("cursor ({}, {})", cursor.x, cursor.y);
return 0;
}
Run it
cd Examples/Types/Struct
rux run
origin (0, 0)
corner (5, 2)
box at (5, 2), 4 by 3
cursor (10, 6)
moved (7, 10)
cursor (10, 6)
Common mistakes
Point { x: 1 } fails with error: initializer for 'Point' is missing required field 'y'. A struct literal names every field; there are no defaults to fall back on. If building a value takes more than listing its fields, give the type a constructor.Point { x: 1, y: 2, z: 3 } fails with error: struct 'Point' has no field 'z', and a note lists the fields the struct does have.let.With
let p = Point { x: 1, y: 2 };, the line p.x = 5; fails with error: cannot modify immutable variable 'p', and the compiler suggests declaring p with var. A field is part of its struct, and a let struct is read-only all the way through.{}.PrintLine("{}", origin) fails with error: argument 2 to 'PrintLine' has type 'Point', but variadic parameter 'args' requires 'Display'. {} knows how to print numbers, text and booleans, not types you declared. Print the fields one by one — or, once you reach Display, teach the type to print itself.Try it yourself
- Write
func Mirrored(point: Point) -> Pointthat swapsxandy, and printMirrored(corner). - Write
func Area(box: Box) -> intand print the area ofbox. - Declare a
Linestruct with twoPointfields,startandend, build one, and print all four numbers with chained dots. - Change
var cursortolet cursorand read the errors on the two assignments.
Learn more
- Structures in the Rux Reference
- Tuples vs structs — when each one fits
- Reference — passing a struct without copying it
- Struct pattern — taking a struct apart in a
match