RustArena

Step 7 of 7

Putting it together: a bank account program

Time to design a type from scratch and drive it from a real program. A good recipe:

  1. Data: which fields does the value need? Which types rule out nonsense (a u64 balance can't be negative)?
  2. Operations: what can you do with it? Each becomes a method, with the weakest receiver that works.
  3. Construction: a new that sets sensible starting values.
  4. Rules: which invariants must always hold, and which methods enforce them?

Then keep main thin: it reads input, calls methods and prints results. The rules live in the type.

struct Scoreboard {
    home: u32,
    away: u32,
}

impl Scoreboard {
    fn new() -> Self {
        Self { home: 0, away: 0 }
    }

    fn score(&mut self, team: &str, points: u32) -> bool {
        if team == "home" {
            self.home += points;
        } else if team == "away" {
            self.away += points;
        } else {
            return false;
        }
        true
    }

    fn summary(&self) -> String {
        format!("home {} - {} away", self.home, self.away)
    }
}

fn main() {
    let events = [("home", 3), ("away", 2), ("visitors", 1), ("home", 1)];
    let mut board = Scoreboard::new();
    for (team, points) in events {
        if !board.score(team, points) {
            println!("ignored unknown team: {}", team);
        }
    }
    println!("{}", board.summary());
}
ignored unknown team: visitors
home 4 - 2 away

Why this split pays off at work: the Scoreboard logic can be tested by calling its methods directly (exactly what the hidden tests on this site have been doing to your code), without typing input or reading printed text. And when the input format changes (a file, a web request), only main changes.

Your task

Write a bank account program. Each line of input is a command:

Input What happens Prints
deposit N adds N to the balance deposited N
withdraw N takes N out if the balance is big enough withdrew N, or insufficient funds (balance unchanged)
balance nothing changes balance: N
anything else nothing changes unknown command: followed by the whole line

The account starts at 0. Every deposit and withdraw line has a valid whole number. Implement the Account methods, then call them from main. For example, this input:

deposit 500
withdraw 200
withdraw 900
balance

prints:

deposited 500
withdrew 200
insufficient funds
balance: 300

Challenge 2

Same idea, different domain: a parking garage. The first line of input is the garage's capacity. Every following line is a command:

Input Prints
enter car in (N/C) with the new count N and capacity C, or full if there's no room
leave car out (N/C), or empty if no car is parked
status N/C parked

The starter reads the capacity for you. Give Garage a constructor and methods (enter and leave return whether they worked), then handle the commands in main.

Challenge 3

Write a to-do list program from scratch. Model it with two structs: a Task (its text and whether it's done) and a TodoList holding a Vec<Task>, with methods for each command. Commands, one per line:

Input What happens Prints
add TEXT adds a task (TEXT is everything after add ) added #N, where N is its number, starting at 1
done N marks task N as done completed #N, or no task N if there's no such task
list each task as N. [x] TEXT (done) or N. [ ] TEXT; nothing to do if the list is empty

Tip: &line[4..] is the part of the line after add (4 bytes), and for i in 0..self.tasks.len() gives you each task's index.

Previous: Method chaining