Guide contents

Lesson 20 of 94

Methods & Associated Functions

In Rust, a struct holds the data and a separate impl block holds the functions attached to it. The key new idea is that every method declares how it uses the instance: read it, change it, or consume it.

Concept Rust
Instance method Method with an explicit first parameter: &self, &mut self, or self
Function without an instance Associated function (no self), called with Type::name()
Constructor Associated function, by convention User::new(...)
The instance and its type self (the value) and Self (the type)
Constant tied to a type Associated constant: const MAX: u32 = 10; inside impl
Privacy Fields are private to their module unless marked pub

1. A method that reads the instance: &self

Rust
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }

    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

fn main() {
    let r = Rectangle { width: 3.0, height: 4.5 };
    println!("area {} square {}", r.area(), r.is_square());
}
Output
area 13.5 square false

&self is short for self: &Self, where Self means "the type this impl is for". The method borrows the rectangle, so r is still usable afterwards.

2. A method that changes the instance: &mut self

Rust
#[derive(Debug)]
struct Counter {
    count: u32,
}

impl Counter {
    fn increment(&mut self) {
        self.count += 1;
    }

    fn reset(&mut self) {
        self.count = 0;
    }
}

fn main() {
    let mut c = Counter { count: 0 };
    c.increment();
    c.increment();
    println!("{:?}", c);
    c.reset();
    println!("{:?}", c);
}
Output
Counter { count: 2 }
Counter { count: 0 }

The signature tells every caller that this method may change the value. The variable must be mut to call it.

3. Calling a &mut self method needs a mut variable

Rust✗ does not compile · E0596
struct Counter {
    count: u32,
}

impl Counter {
    fn increment(&mut self) {
        self.count += 1;
    }
}

fn main() {
    let c = Counter { count: 0 };
    c.increment();
}
Output
error[E0596]: cannot borrow `c` as mutable, as it is not declared as mutable
  --> src/main.rs:13:5
   |
13 |     c.increment();
   |     ^ cannot borrow as mutable
   |
help: consider changing this to be mutable
   |
12 |     let mut c = Counter { count: 0 };
   |         +++

4. A method that consumes the instance: self

A method taking self by value takes ownership. After the call, the instance is gone. Use this for "finishing" operations that turn a value into something else.

Rust
struct Draft {
    title: String,
    body: String,
}

struct Published {
    html: String,
}

impl Draft {
    fn publish(self) -> Published {
        Published { html: format!("<h1>{}</h1><p>{}</p>", self.title, self.body) }
    }
}

fn main() {
    let draft = Draft { title: String::from("Hello"), body: String::from("First post") };
    let post = draft.publish();
    println!("{}", post.html);
}
Output
<h1>Hello</h1><p>First post</p>

Using draft after publish is an error, which is exactly what you want: a draft cannot be edited after it has been published.

Rust✗ does not compile · E0382
struct Draft {
    title: String,
}

impl Draft {
    fn publish(self) -> String {
        format!("<h1>{}</h1>", self.title)
    }
}

fn main() {
    let draft = Draft { title: String::from("Hello") };
    let html = draft.publish();
    println!("{html}, {}", draft.title);
}
Output
error[E0382]: borrow of moved value: `draft`
  --> src/main.rs:14:28
   |
12 |     let draft = Draft { title: String::from("Hello") };
   |         ----- move occurs because `draft` has type `Draft`, which does not implement the `Copy` trait
13 |     let html = draft.publish();
   |                      --------- `draft` moved due to this method call
14 |     println!("{html}, {}", draft.title);
   |                            ^^^^^^^^^^^ value borrowed here after move
   |
note: `Draft::publish` takes ownership of the receiver `self`, which moves `draft`
  --> src/main.rs:6:16
   |
 6 |     fn publish(self) -> String {
   |                ^^^^

5. Associated functions: Rust's static methods and constructors

A function in an impl block without self is an associated function. You call it with Type::name(...). By convention, the main constructor is called new.

Rust
#[derive(Debug)]
struct User {
    name: String,
    email: String,
    active: bool,
}

impl User {
    fn new(name: &str, email: &str) -> Self {
        Self {
            name: name.to_string(),
            email: email.to_lowercase(),
            active: true,
        }
    }
}

fn main() {
    let user = User::new("Ada", "Ada@Example.com");
    println!("{:?}", user);
}
Output
User { name: "Ada", email: "ada@example.com", active: true }

Self inside an impl block is an alias for the type (User here). new is only a convention: it is not a keyword and has no special powers.

6. Several constructors with descriptive names

Without overloading, each constructor gets its own name. The standard library uses the same style: String::new, String::from, String::with_capacity.

Rust
#[derive(Debug)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

impl Color {
    fn rgb(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b }
    }

    fn gray(level: u8) -> Self {
        Self { r: level, g: level, b: level }
    }

    fn from_hex(hex: u32) -> Self {
        Self {
            r: ((hex >> 16) & 0xFF) as u8,
            g: ((hex >> 8) & 0xFF) as u8,
            b: (hex & 0xFF) as u8,
        }
    }

    fn black() -> Self {
        Self::gray(0)
    }
}

fn main() {
    println!("{:?}", Color::rgb(255, 128, 0));
    println!("{:?}", Color::gray(200));
    println!("{:?}", Color::from_hex(0x1E90FF));
    println!("{:?}", Color::black());
}
Output
Color { r: 255, g: 128, b: 0 }
Color { r: 200, g: 200, b: 200 }
Color { r: 30, g: 144, b: 255 }
Color { r: 0, g: 0, b: 0 }

7. Getters use the field name, without get_

Rust convention names a getter after the field. A method and a field can share a name, and parentheses tell them apart.

Rust
struct Temperature {
    celsius: f64,
}

impl Temperature {
    fn celsius(&self) -> f64 {
        self.celsius
    }

    fn fahrenheit(&self) -> f64 {
        self.celsius * 9.0 / 5.0 + 32.0
    }
}

fn main() {
    let t = Temperature { celsius: 21.5 };
    println!("field {} / method {} / derived {}", t.celsius, t.celsius(), t.fahrenheit());
}
Output
field 21.5 / method 21.5 / derived 70.7

8. Methods that call other methods

Rust
struct Invoice {
    amounts_cents: Vec<u64>,
    tax_percent: u64,
}

impl Invoice {
    fn subtotal(&self) -> u64 {
        let mut sum = 0;
        for a in &self.amounts_cents {
            sum += a;
        }
        sum
    }

    fn tax(&self) -> u64 {
        self.subtotal() * self.tax_percent / 100
    }

    fn total(&self) -> u64 {
        self.subtotal() + self.tax()
    }
}

fn main() {
    let inv = Invoice { amounts_cents: vec![1_000, 2_500, 499], tax_percent: 20 };
    println!("subtotal={} tax={} total={}", inv.subtotal(), inv.tax(), inv.total());
}
Output
subtotal=3999 tax=799 total=4798

9. Methods that take another instance

Rust
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width >= other.width && self.height >= other.height
    }

    fn scaled(&self, factor: u32) -> Rectangle {
        Rectangle { width: self.width * factor, height: self.height * factor }
    }
}

fn main() {
    let box_ = Rectangle { width: 30, height: 50 };
    let small = Rectangle { width: 10, height: 40 };
    let large = small.scaled(4);
    println!("box can hold small: {}", box_.can_hold(&small));
    println!("box can hold large ({}x{}): {}", large.width, large.height, box_.can_hold(&large));
}
Output
box can hold small: true
box can hold large (40x160): false

10. Automatic referencing at the call site

You write r.area(), not (&r).area(). Rust adds the &, &mut, or * needed to match the method's self type. This works through references too.

Rust
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn distance_to(&self, other: &Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

fn main() {
    let a = Point { x: 0.0, y: 0.0 };
    let b = Point { x: 6.0, y: 8.0 };
    let ref_a = &a;
    let ref_ref_a = &ref_a;
    println!("{}", a.distance_to(&b));
    println!("{}", ref_a.distance_to(&b));
    println!("{}", ref_ref_a.distance_to(&b));
    println!("{}", Point::distance_to(&a, &b)); // the fully explicit form
}
Output
10
10
10
10

The last line shows what a method call really is: an associated function whose first argument is the instance.

11. Associated constants

Rust
struct Password {
    value: String,
}

impl Password {
    const MIN_LENGTH: usize = 12;
    const MAX_LENGTH: usize = 128;

    fn is_valid(&self) -> bool {
        let len = self.value.len();
        len >= Self::MIN_LENGTH && len <= Self::MAX_LENGTH
    }
}

fn main() {
    let p = Password { value: String::from("correct horse battery") };
    println!("valid: {} (min {})", p.is_valid(), Password::MIN_LENGTH);
}
Output
valid: true (min 12)

12. Several impl blocks

A type can have any number of impl blocks. This is often used to group related methods.

Rust
struct Circle {
    radius: f64,
}

impl Circle {
    fn new(radius: f64) -> Self {
        Self { radius }
    }
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }

    fn circumference(&self) -> f64 {
        2.0 * std::f64::consts::PI * self.radius
    }
}

fn main() {
    let c = Circle::new(2.0);
    println!("area {:.2}, circumference {:.2}", c.area(), c.circumference());
}
Output
area 12.57, circumference 12.57

13. Methods on tuple structs and newtypes

Rust
#[derive(Debug, Clone, Copy)]
struct Celsius(f64);

#[derive(Debug, Clone, Copy)]
struct Fahrenheit(f64);

impl Celsius {
    fn to_fahrenheit(self) -> Fahrenheit {
        Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
    }
}

impl Fahrenheit {
    fn to_celsius(self) -> Celsius {
        Celsius((self.0 - 32.0) * 5.0 / 9.0)
    }
}

fn main() {
    let body = Celsius(37.0);
    let f = body.to_fahrenheit();
    println!("{:?} = {:?}", body, f);
    println!("{:?}", Fahrenheit(212.0).to_celsius());
}
Output
Celsius(37.0) = Fahrenheit(98.6)
Celsius(100.0)

For a small Copy type like this, taking self by value is idiomatic: copying 8 bytes is as cheap as passing a reference.

14. Chaining &mut self methods

A method can return &mut Self so calls can be chained on the same instance.

Rust
#[derive(Debug, Default)]
struct Query {
    table: String,
    conditions: Vec<String>,
    limit: Option<u32>,
}

impl Query {
    fn from(&mut self, table: &str) -> &mut Self {
        self.table = table.to_string();
        self
    }

    fn filter(&mut self, condition: &str) -> &mut Self {
        self.conditions.push(condition.to_string());
        self
    }

    fn limit(&mut self, n: u32) -> &mut Self {
        self.limit = Some(n);
        self
    }

    fn to_sql(&self) -> String {
        let mut sql = format!("SELECT * FROM {}", self.table);
        if !self.conditions.is_empty() {
            sql.push_str(&format!(" WHERE {}", self.conditions.join(" AND ")));
        }
        if let Some(n) = self.limit {
            sql.push_str(&format!(" LIMIT {n}"));
        }
        sql
    }
}

fn main() {
    let mut q = Query::default();
    q.from("orders").filter("status = 'paid'").filter("total > 100").limit(20);
    println!("{}", q.to_sql());
}
Output
SELECT * FROM orders WHERE status = 'paid' AND total > 100 LIMIT 20

limit: Option<u32> holds either Some(n) or None, and if let Some(n) = ... runs only when there is a value. Both are covered in Option.

15. A consuming builder

The other builder style takes and returns self by value. The final build(self) consumes the builder. This is the most common builder pattern in Rust libraries.

Rust
#[derive(Debug)]
struct HttpClient {
    base_url: String,
    timeout_secs: u32,
    retries: u8,
    user_agent: String,
}

struct HttpClientBuilder {
    base_url: String,
    timeout_secs: u32,
    retries: u8,
    user_agent: String,
}

impl HttpClientBuilder {
    fn new(base_url: &str) -> Self {
        Self {
            base_url: base_url.to_string(),
            timeout_secs: 30,
            retries: 0,
            user_agent: String::from("rust-client"),
        }
    }

    fn timeout(mut self, secs: u32) -> Self {
        self.timeout_secs = secs;
        self
    }

    fn retries(mut self, n: u8) -> Self {
        self.retries = n;
        self
    }

    fn build(self) -> HttpClient {
        HttpClient {
            base_url: self.base_url,
            timeout_secs: self.timeout_secs,
            retries: self.retries,
            user_agent: self.user_agent,
        }
    }
}

fn main() {
    let client = HttpClientBuilder::new("https://api.example.com")
        .timeout(5)
        .retries(3)
        .build();
    println!("{:#?}", client);
}
Output
HttpClient {
    base_url: "https://api.example.com",
    timeout_secs: 5,
    retries: 3,
    user_agent: "rust-client",
}

mut self makes the owned self mutable inside the method, like mut on any other parameter.

16. A &mut self call while a field is borrowed

The borrow rules apply to self like any other value. Holding a reference into the struct blocks methods that need &mut self.

Rust✗ does not compile · E0502
struct Playlist {
    songs: Vec<String>,
}

impl Playlist {
    fn add(&mut self, song: &str) {
        self.songs.push(song.to_string());
    }
}

fn main() {
    let mut p = Playlist { songs: vec![String::from("Intro")] };
    let first = &p.songs[0];
    p.add("Outro");
    println!("{first}");
}
Output
error[E0502]: cannot borrow `p` as mutable because it is also borrowed as immutable
  --> src/main.rs:14:5
   |
13 |     let first = &p.songs[0];
   |                  ------- immutable borrow occurs here
14 |     p.add("Outro");
   |     ^^^^^^^^^^^^^^ mutable borrow occurs here
15 |     println!("{first}");
   |                ----- immutable borrow later used here

17. Returning references from methods

A method can return a reference into self. The compiler knows it borrows from self.

Rust
struct Library {
    books: Vec<(String, u32)>, // (title, year)
}

impl Library {
    fn oldest(&self) -> &str {
        let mut best = &self.books[0];
        for book in &self.books {
            if book.1 < best.1 {
                best = book;
            }
        }
        &best.0
    }

    fn titles(&self) -> Vec<&str> {
        let mut titles = Vec::new();
        for (title, _) in &self.books {
            titles.push(title.as_str());
        }
        titles
    }
}

fn main() {
    let lib = Library {
        books: vec![
            (String::from("Dune"), 1965),
            (String::from("Neuromancer"), 1984),
            (String::from("Foundation"), 1951),
        ],
    };
    println!("oldest: {}", lib.oldest());
    println!("titles: {:?}", lib.titles());
}
Output
oldest: Foundation
titles: ["Dune", "Neuromancer", "Foundation"]

18. Returning a &mut to a field

Rust
#[derive(Debug)]
struct Inventory {
    items: Vec<(String, u32)>,
}

impl Inventory {
    fn quantity_mut(&mut self, name: &str) -> &mut u32 {
        let mut index = self.items.len();
        for (i, item) in self.items.iter().enumerate() {
            if item.0 == name {
                index = i;
            }
        }
        if index == self.items.len() {
            self.items.push((name.to_string(), 0));
        }
        &mut self.items[index].1
    }
}

fn main() {
    let mut inv = Inventory { items: vec![(String::from("apple"), 3)] };
    *inv.quantity_mut("apple") += 2;
    *inv.quantity_mut("pear") += 7;
    println!("{:?}", inv);
}
Output
Inventory { items: [("apple", 5), ("pear", 7)] }

The method finds the index first and only then creates the &mut. The more obvious version, which returns &mut item.1 from inside the for loop and pushes after the loop, is rejected by today's borrow checker even though it is actually safe. This is a known limitation, and Fixing Borrow Checker Errors shows it and its workarounds.

19. Privacy: fields are private to their module

In one file with no modules, everything can see everything. Privacy in Rust works at the level of modules, which you declare with mod. Items inside a module are private unless marked pub. Here the balance field is private, so code outside the bank module must go through the methods. Modules covers this fully.

Rust
mod bank {
    pub struct Account {
        pub owner: String,
        balance_cents: i64, // private
    }

    impl Account {
        pub fn open(owner: &str) -> Self {
            Self { owner: owner.to_string(), balance_cents: 0 }
        }

        pub fn deposit(&mut self, cents: i64) {
            if cents > 0 {
                self.balance_cents += cents;
            }
        }

        pub fn balance(&self) -> i64 {
            self.balance_cents
        }
    }
}

fn main() {
    let mut acc = bank::Account::open("Ana");
    acc.deposit(5_000);
    acc.deposit(-9_999); // ignored by the method's rule
    println!("{} has {}", acc.owner, acc.balance());
}
Output
Ana has 5000

Reaching for the private field directly does not compile:

Rust✗ does not compile · E0616
mod bank {
    pub struct Account {
        balance_cents: i64,
    }

    impl Account {
        pub fn open() -> Self {
            Self { balance_cents: 0 }
        }
    }
}

fn main() {
    let mut acc = bank::Account::open();
    acc.balance_cents = 1_000_000;
}
Output
error[E0616]: field `balance_cents` of struct `Account` is private
  --> src/main.rs:15:9
   |
15 |     acc.balance_cents = 1_000_000;
   |         ^^^^^^^^^^^^^ private field

20. Realistic example: a bank account with history

Rust
#[derive(Debug)]
struct Account {
    owner: String,
    balance_cents: i64,
    history: Vec<String>,
}

impl Account {
    const OVERDRAFT_LIMIT_CENTS: i64 = 10_000;

    fn open(owner: &str, initial_cents: i64) -> Self {
        let mut acc = Self { owner: owner.to_string(), balance_cents: 0, history: Vec::new() };
        acc.deposit(initial_cents);
        acc
    }

    fn deposit(&mut self, cents: i64) {
        self.balance_cents += cents;
        self.history.push(format!("+{}", Self::fmt(cents)));
    }

    fn withdraw(&mut self, cents: i64) -> bool {
        if self.balance_cents - cents < -Self::OVERDRAFT_LIMIT_CENTS {
            self.history.push(format!("DECLINED -{}", Self::fmt(cents)));
            return false;
        }
        self.balance_cents -= cents;
        self.history.push(format!("-{}", Self::fmt(cents)));
        true
    }

    fn fmt(cents: i64) -> String {
        format!("{}.{:02}", cents / 100, (cents % 100).abs())
    }

    fn statement(&self) -> String {
        format!("{}: {} [{}]", self.owner, Self::fmt(self.balance_cents), self.history.join(", "))
    }
}

fn main() {
    let mut acc = Account::open("Grace", 50_00);
    acc.withdraw(120_00);
    acc.withdraw(50_00);
    acc.deposit(25_50);
    println!("{}", acc.statement());
}
Output
Grace: -44.50 [+50.00, -120.00, DECLINED -50.00, +25.50]

21. Realistic example: a bounded stack

A stack that wraps a Vec and enforces a maximum size. pop and peek return Option because the stack may be empty.

Rust
struct BoundedStack {
    items: Vec<i32>,
    capacity: usize,
}

impl BoundedStack {
    fn with_capacity(capacity: usize) -> Self {
        Self { items: Vec::with_capacity(capacity), capacity }
    }

    fn push(&mut self, value: i32) -> bool {
        if self.items.len() == self.capacity {
            return false;
        }
        self.items.push(value);
        true
    }

    fn pop(&mut self) -> Option<i32> {
        self.items.pop()
    }

    fn peek(&self) -> Option<&i32> {
        self.items.last()
    }

    fn len(&self) -> usize {
        self.items.len()
    }

    fn is_full(&self) -> bool {
        self.items.len() == self.capacity
    }
}

fn main() {
    let mut stack = BoundedStack::with_capacity(3);
    for v in [10, 20, 30, 40] {
        println!("push {v}: {}", stack.push(v));
    }
    println!("full: {}, len: {}, peek: {:?}", stack.is_full(), stack.len(), stack.peek());
    println!("pop: {:?}", stack.pop());
    println!("pop: {:?}", stack.pop());
    println!("len: {}", stack.len());
}
Output
push 10: true
push 20: true
push 30: true
push 40: false
full: true, len: 3, peek: Some(30)
pop: Some(30)
pop: Some(20)
len: 1

22. Realistic example: a shopping cart that checks out once

checkout(self) consumes the cart and returns a receipt. After checkout, the compiler makes it impossible to add items to the old cart or check out twice.

Rust
#[derive(Debug)]
struct Receipt {
    lines: Vec<String>,
    total_cents: u64,
}

struct Cart {
    items: Vec<(String, u64, u32)>, // (name, unit price, quantity)
}

impl Cart {
    fn new() -> Self {
        Self { items: Vec::new() }
    }

    fn add(&mut self, name: &str, price_cents: u64, quantity: u32) {
        for item in &mut self.items {
            if item.0 == name {
                item.2 += quantity;
                return;
            }
        }
        self.items.push((name.to_string(), price_cents, quantity));
    }

    fn remove(&mut self, name: &str) {
        let mut i = 0;
        while i < self.items.len() {
            if self.items[i].0 == name {
                self.items.remove(i);
            } else {
                i += 1;
            }
        }
    }

    fn checkout(self) -> Receipt {
        let mut lines = Vec::new();
        let mut total = 0;
        for (name, price, qty) in self.items {
            let line_total = price * qty as u64;
            lines.push(format!("{qty} x {name} = {}.{:02}", line_total / 100, line_total % 100));
            total += line_total;
        }
        Receipt { lines, total_cents: total }
    }
}

fn main() {
    let mut cart = Cart::new();
    cart.add("coffee", 899, 1);
    cart.add("mug", 1250, 2);
    cart.add("coffee", 899, 1);
    cart.add("filter", 350, 1);
    cart.remove("filter");
    let receipt = cart.checkout();
    for line in &receipt.lines {
        println!("{line}");
    }
    println!("total {}.{:02}", receipt.total_cents / 100, receipt.total_cents % 100);
}
Output
2 x coffee = 17.98
2 x mug = 25.00
total 42.98

23. Realistic example: a stopwatch with laps

Time is passed in as milliseconds, which keeps the example deterministic. Real code would use std::time::Instant.

Rust
struct Stopwatch {
    started_at_ms: u64,
    laps: Vec<u64>,
    last_lap_at_ms: u64,
}

impl Stopwatch {
    fn start(now_ms: u64) -> Self {
        Self { started_at_ms: now_ms, laps: Vec::new(), last_lap_at_ms: now_ms }
    }

    fn lap(&mut self, now_ms: u64) -> u64 {
        let lap = now_ms - self.last_lap_at_ms;
        self.laps.push(lap);
        self.last_lap_at_ms = now_ms;
        lap
    }

    fn fastest_lap(&self) -> u64 {
        let mut best = u64::MAX;
        for &lap in &self.laps {
            best = best.min(lap);
        }
        best
    }

    fn stop(self, now_ms: u64) -> (u64, Vec<u64>) {
        (now_ms - self.started_at_ms, self.laps)
    }
}

fn main() {
    let mut sw = Stopwatch::start(1_000);
    sw.lap(62_400);
    sw.lap(120_900);
    sw.lap(181_150);
    println!("fastest lap: {} ms", sw.fastest_lap());
    let (total, laps) = sw.stop(185_000);
    println!("total {total} ms, laps {:?}", laps);
}
Output
fastest lap: 58500 ms
total 184000 ms, laps [61400, 58500, 60250]

24. Realistic example: a 2x2 matrix type

Small math types usually take self by value and return new values, which makes expressions read naturally.

Rust
#[derive(Debug, Clone, Copy, PartialEq)]
struct Matrix2 {
    a: f64,
    b: f64,
    c: f64,
    d: f64,
}

impl Matrix2 {
    const IDENTITY: Matrix2 = Matrix2 { a: 1.0, b: 0.0, c: 0.0, d: 1.0 };

    fn new(a: f64, b: f64, c: f64, d: f64) -> Self {
        Self { a, b, c, d }
    }

    fn mul(self, o: Matrix2) -> Matrix2 {
        Matrix2::new(
            self.a * o.a + self.b * o.c,
            self.a * o.b + self.b * o.d,
            self.c * o.a + self.d * o.c,
            self.c * o.b + self.d * o.d,
        )
    }

    fn determinant(self) -> f64 {
        self.a * self.d - self.b * self.c
    }

    fn pow(self, n: u32) -> Matrix2 {
        let mut result = Self::IDENTITY;
        for _ in 0..n {
            result = result.mul(self);
        }
        result
    }
}

fn main() {
    let fib = Matrix2::new(1.0, 1.0, 1.0, 0.0);
    println!("fib^10 = {:?}", fib.pow(10));
    println!("det = {}", fib.determinant());
    println!("m * I == m: {}", fib.mul(Matrix2::IDENTITY) == fib);
}
Output
fib^10 = Matrix2 { a: 89.0, b: 55.0, c: 55.0, d: 34.0 }
det = -1
m * I == m: true

The top-right entry of fib^10 is the 10th Fibonacci number, 55. Advanced Traits shows how to let a * b call mul with operator overloading.

25. Realistic example: a rate limiter

A token bucket allows short bursts but limits the average rate. Timestamps are passed in to keep the example deterministic.

Rust
struct TokenBucket {
    capacity: f64,
    tokens: f64,
    refill_per_sec: f64,
    last_refill_ms: u64,
}

impl TokenBucket {
    fn new(capacity: u32, refill_per_sec: f64, now_ms: u64) -> Self {
        Self { capacity: capacity as f64, tokens: capacity as f64, refill_per_sec, last_refill_ms: now_ms }
    }

    fn refill(&mut self, now_ms: u64) {
        let elapsed_secs = (now_ms - self.last_refill_ms) as f64 / 1000.0;
        self.tokens = (self.tokens + elapsed_secs * self.refill_per_sec).min(self.capacity);
        self.last_refill_ms = now_ms;
    }

    fn try_acquire(&mut self, now_ms: u64) -> bool {
        self.refill(now_ms);
        if self.tokens >= 1.0 {
            self.tokens -= 1.0;
            true
        } else {
            false
        }
    }
}

fn main() {
    let mut limiter = TokenBucket::new(3, 1.0, 0);
    let requests_at_ms = [0, 10, 20, 30, 500, 1_100, 1_200, 3_500];
    for t in requests_at_ms {
        let verdict = if limiter.try_acquire(t) { "allowed" } else { "throttled" };
        println!("t={t:>5}ms {verdict}");
    }
}
Output
t=    0ms allowed
t=   10ms allowed
t=   20ms allowed
t=   30ms throttled
t=  500ms throttled
t= 1100ms allowed
t= 1200ms throttled
t= 3500ms allowed

26. Realistic example: a type-safe state change

Consuming methods can model a workflow where each step returns a different type. An unpaid order simply has no ship method, so shipping an unpaid order is a compile error rather than a runtime check.

Rust
struct NewOrder {
    id: u32,
    total_cents: u64,
}

struct PaidOrder {
    id: u32,
    payment_ref: String,
}

struct ShippedOrder {
    id: u32,
    tracking: String,
}

impl NewOrder {
    fn pay(self, payment_ref: &str) -> PaidOrder {
        println!("order {} paid {} cents", self.id, self.total_cents);
        PaidOrder { id: self.id, payment_ref: payment_ref.to_string() }
    }
}

impl PaidOrder {
    fn ship(self, tracking: &str) -> ShippedOrder {
        println!("order {} (payment {}) shipped", self.id, self.payment_ref);
        ShippedOrder { id: self.id, tracking: tracking.to_string() }
    }
}

fn main() {
    let order = NewOrder { id: 77, total_cents: 4_200 };
    // order.ship("X"); // does not compile: NewOrder has no `ship` method
    let paid = order.pay("pay_abc123");
    let shipped = paid.ship("1Z999AA10123456784");
    println!("order {} tracking {}", shipped.id, shipped.tracking);
}
Output
order 77 paid 4200 cents
order 77 (payment pay_abc123) shipped
order 77 tracking 1Z999AA10123456784

This is the typestate pattern. API Design Idioms builds on it.

Summary

  • Methods live in impl Type { ... } blocks. A type can have several impl blocks.
  • The first parameter says how the method uses the instance: &self reads, &mut self changes, self consumes.
  • Functions without self are associated functions, called as Type::name(). new is the conventional constructor name.
  • Self is the type being implemented. Associated constants live in impl blocks too.
  • Method calls add &, &mut, or * automatically.
  • Builders return &mut Self (chaining on one instance) or Self (consuming builder).
  • Privacy is per module: fields are private unless marked pub.

Next: Enums.