Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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) or Ok(T)).
  • Associated functions named new, zero, default, or origin are conventional constructors.
  • There is no struct update syntax or spread syntax (..other is not supported).
  • There is no inheritance. Compose structs or use traits for shared behavior.