Structs and Enums
Structs
pub struct Point {
x: i32
y: i32
}
Fields are declared one per line (no commas in declarations). Use pub to export the struct. Struct fields are private by default.
Construction
let p = Point { x: 10, y: 20 }
Field shorthand: when a local binding name matches the field name:
let x = 10
let y = 20
let p = Point { x, y } // equivalent to Point { x: x, y: y }
Field Access and Mutation
let n = p.x // read field
p.x = 15 // error: p is immutable
let mut p2 = Point { x: 10, y: 20 }
p2.x = 15 // ok
Methods
Methods are defined in impl blocks. The receiver mode controls mutability:
impl Point {
// Read-only method
fn distance_from_origin(self) -> i32 {
return self.x * self.x + self.y * self.y
}
// Mutating method
fn translate(mut self, dx: i32, dy: i32) {
self.x = self.x + dx
self.y = self.y + dy
}
// Consuming method
fn into_array(owned self) -> [i32; 2] {
return [self.x, self.y]
}
}
Call a mutable method on a mut binding:
let mut p = Point { x: 0, y: 0 }
p.translate(5, 3) // ok
p.distance_from_origin() // ok, read-only
let q = Point { x: 0, y: 0 }
q.translate(5, 3) // error: q is immutable
Within an impl block, Self is a stable alias for the implementing type. It can be used in method signatures, return types, struct construction, and enum variant paths:
impl Point {
fn origin() -> Self {
return Self { x: 0, y: 0 }
}
fn clone(self) -> Self {
return Self { x: self.x, y: self.y }
}
}
Using Self instead of the concrete type name means that if the type is renamed, all the method signatures remain valid without changes.
Associated Functions (“Static Methods”)
Functions in impl blocks without self are called with Type.function():
impl Point {
fn origin() -> Point {
return Point { x: 0, y: 0 }
}
fn new(x: i32, y: i32) -> Point {
return Point { x, y }
}
}
let p = Point.origin()
let q = Point.new(3, 4)
Enums
Enums are sum types: a value is exactly one of the listed variants.
Unit Variants
enum Direction {
North
South
East
West
}
let d = Direction.North
Named-Field Variants
enum Token {
Ident { text: string }
Number { value: i64 }
Eof
}
let t = Token.Ident { text: "gpio" }
let t = Token.Ident { text } // shorthand
Positional Variants
enum Message {
Ready
Data(u8, u16)
Error(string)
}
let m = Message.Data(1, 512)
Use positional variants for compact, obvious payloads. Prefer named fields when the meaning of each field benefits from a name.
Pattern Matching Enums
match token {
Token.Ident { text } => process_ident(text)
Token.Number { value } => process_number(value)
Token.Eof => return
}
match message {
Message.Ready => start()
Message.Data(kind, len) => handle(kind, len)
Message.Error(msg) => log.error(msg)
}
Positional payload values are accessed through pattern binding, not .0, .1 or similar.
Backed Enums
Unit-only enums may declare an explicit primitive backing type. Variants auto-increment from 0 unless given an explicit value:
enum Direction: u8 {
North // 0
South = 4 // 4
East // 5
West // 6
}
Supported backing types are integers, floats, and char. char-backed enums require an explicit value on every variant. Backed enums may only contain unit variants.
Enum Methods
Enums can also have impl blocks. Self refers to the enum type and can be used to construct variants:
impl Direction {
fn opposite(self) -> Self {
match self {
Direction.North => Self.South
Direction.South => Self.North
Direction.East => Self.West
Direction.West => Self.East
}
}
}
impl Token {
fn eof() -> Self {
return Self.Eof
}
fn ident(text: string) -> Self {
return Self.Ident { text }
}
}
Conventions
- Use structs for data with named fields that carry meaning.
- Use enums for values that can be one of several distinct shapes.
- Prefer named-field enum variants unless the payload is obviously ordered (like
Some(T)orOk(T)). - Associated functions named
new,zero,default, ororiginare conventional constructors. - There is no struct update syntax or spread syntax (
..otheris not supported). - There is no inheritance. Compose structs or use traits for shared behavior.