RustArena

Step 5 of 7

Getters, invariants and multiple impl blocks

A getter is a method that just returns a field. In Rust it's named after the field, with no get_ prefix: balance(), not get_balance() (that's the official API guideline, and the standard library follows it everywhere: v.len(), s.capacity()). A method can share its name with a field: hp.current is the field, hp.current() the method.

For a String field, return a borrowed &str so callers can look without copying: fn name(&self) -> &str { &self.name }. (&self.name is a &String, which turns into &str automatically, as in the slices module.)

Why not just read the field?

Inside one file you can. But at work, structs are shared between modules and teams, and their fields are usually private (the modules module shows how). Then the methods are the only way in, which lets the type keep its invariants: rules that are always true for every value of the type. "current never exceeds max." "A balance is never negative." "An email always contains @." If every change goes through a method that checks the rule, no code anywhere can break it.

struct Health {
    current: u32,
    max: u32,
}

impl Health {
    fn new(max: u32) -> Self {
        Self { current: max, max }
    }

    fn current(&self) -> u32 {
        self.current
    }

    fn max(&self) -> u32 {
        self.max
    }
}

impl Health {
    fn heal(&mut self, amount: u32) {
        self.current = (self.current + amount).min(self.max);
    }

    fn damage(&mut self, amount: u32) {
        self.current = self.current.saturating_sub(amount);
    }

    fn percent(&self) -> u32 {
        self.current * 100 / self.max
    }
}

fn main() {
    let mut hp = Health::new(80);
    hp.damage(50);
    println!("{}/{} ({}%)", hp.current(), hp.max(), hp.percent());
    hp.heal(100);
    println!("{}/{} ({}%)", hp.current(), hp.max(), hp.percent());
}
30/80 (37%)
80/80 (100%)

Multiple impl blocks

Notice there are two impl Health blocks. That's allowed, and it means exactly the same as one big block. Teams use it to group methods (constructors and getters here, game logic there). You'll see more of it later: generic code and traits each get their own impl blocks.

Your task

The User struct has a constructor. In a second impl block, add:

Challenge 2

A volume control with the invariant "the level is never above 100". Write:

Challenge 3

A shopping cart with a size limit. Write two impl blocks for Cart: one with the constructor and getters, one with the rest.

Previous: self: methods that take ownership Next: Method chaining