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:
- Data: which fields does the value need? Which types rule out nonsense (a
u64balance can't be negative)? - Operations: what can you do with it? Each becomes a method, with the weakest receiver that works.
- Construction: a
newthat sets sensible starting values. - 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.