09 · Data Structures
Structs
Records with named fields, nesting, mutation, and methods via `impl`.
On this page
A struct groups related data with explicit field names.
The type name is required at construction — Point { x: 1, y: 2 }, never
just { x: 1, y: 2 }.
Definition, construction, field access with ., nested structs, arrays as fields, and field mutation.
// Feature: Structs — named records (typed fields)
// Syntax: `struct Name { field: T, ... }`
// Construction: `Name { field: value, ... }`
// When to use: group related data with clear names; pass an
// "object" without needing a full class.
// Basic definition.
struct User {
name: str,
age: int,
email: str,
}
// Create an instance — the struct name is REQUIRED.
let u = User { name: "Alice", age: 30, email: "[email protected]" }
// Dot access.
print(u.name) // Alice
print(u.age) // 30
print(u.email) // [email protected]
// Nested structs.
struct Address {
city: str,
country: str,
}
struct Account {
user: User,
address: Address,
}
let acc = Account { user: User { name: "Bob", age: 25, email: "[email protected]" }, address: Address { city: "Curitiba", country: "BR" } }
print(acc.user.name) // Bob
print(acc.address.city) // Curitiba
// Struct with array field.
struct Team {
name: str,
members: [str],
}
let t = Team { name: "Zolo Core", members: ["alice", "bob", "carol"] }
print(t.name)
for m in t.members {
print(" - {m}")
}
// Field mutation (a non-immutable struct is a "mutable" instance by default
// inside methods; see 05-structs-with-impl).
struct Counter {
value: int,
}
let c = Counter { value: 0 }
c.value = c.value + 1
c.value = c.value + 1
print(c.value) // 2
Methods with impl
To add behaviour to a struct, use an impl block. Functions without
self are associated functions (constructors); functions with self are
instance methods:
Rectangle::new, area, perimeter, scale; multiple impl blocks for Stack.
// Feature: Structs with `impl` — methods and constructors
// Syntax: `impl Name { fn method(self, ...) -> R { ... } }`
// Associated constructor: `fn new(...) -> Name { ... }` (no `self`).
// When to use: give behavior to the data type, encapsulate
// invariants, implement an OO-style API.
use std::Array
struct Rectangle {
width: float,
height: float,
}
impl Rectangle {
// Associated function (no `self`) — called via `Rectangle::new`.
fn new(w: float, h: float) -> Rectangle {
return Rectangle { width: w, height: h }
}
// Instance method — first arg is `self`.
fn area(self) -> float {
return self.width * self.height
}
fn perimeter(self) -> float {
return 2.0 * (self.width + self.height)
}
fn is_square(self) -> bool {
return self.width == self.height
}
// Method that returns a new struct (functional immutability).
fn scale(self, factor: float) -> Rectangle {
return Rectangle { width: self.width * factor, height: self.height * factor }
}
}
let r = Rectangle::new(10.0, 5.0)
print(r.area()) // 50
print(r.perimeter()) // 30
print(r.is_square()) // false
let big = r.scale(2.0)
print(big.area()) // 200
print(big.width) // 20
// Multiple `impl` blocks — you can split by topic.
struct Stack {
items: [int],
}
impl Stack {
fn new() -> Stack {
return Stack { items: [] }
}
}
impl Stack {
fn push_item(self, x: int) {
self.items.push(x)
}
fn pop_item(self) -> int {
return self.items.pop() ?? 0
}
fn size(self) -> int {
return self.items.len()
}
}
let s = Stack::new()
s.push_item(1)
s.push_item(2)
s.push_item(3)
print(s.size()) // 3
print(s.pop_item()) // 3
print(s.size()) // 2
Stored-field accessors
Append an accessor block to a stored field when reads or later assignments need
custom behavior without changing value.field call sites. get is required;
omitting set makes the field read-only after construction. Inside either body,
field means the raw stored slot.
Shorthand accessors, a validating setter, a custom getter, raw initialization, and compound assignment.
// Stored-field accessors keep the usual `value.field` API while controlling
// reads and writes. Inside an accessor, `field` means the raw stored slot.
pub struct Score {
pub points: int = 0 {
get
set(value) {
if value < 0 {
field = 0
return
}
field = value
}
},
pub doubled: int = 1 {
get {
return field * 2
}
},
}
fn main() {
// Construction writes raw storage; setters run on later assignments.
let score = Score { points: 12, doubled: 4 }
print(score.points)
print(score.doubled)
score.points = -3
print(score.points)
// Compound assignment reads once through get, then writes through set.
score.points += 5
print(score.points)
}
main()
// expected: 12
// expected: 8
// expected: 0
// expected: 5
A few details:
- Associated functions are called as
TypeName::fn(args). - Methods are called as
instance.method(args). - You can split an
implacross multiple blocks — the compiler merges them.
Challenge
Add a method describe(self) -> str to Rectangle that returns
"rectangle {width}x{height}" and print it for two different rectangles.