Step 7 of 7
Choosing how to pass a value
You now have four ways to hand a value to a function. Choosing well is most of what "idiomatic Rust" means for function signatures:
| The function... | Parameter | Example |
|---|---|---|
| only reads the value | &T |
fn total(items: &Vec<u32>) -> u32 |
| changes the value in place | &mut T |
fn restock(items: &mut Vec<u32>, n: u32) |
| keeps the value, or uses it up | T (move) |
fn archive(report: String) |
gets a small Copy value |
T (copy) |
fn is_adult(age: u32) -> bool |
| creates new data | returns an owned T |
fn make_label(...) -> String |
Here's a bank account that uses all of them:
fn print_statement(owner: &String, history: &Vec<i64>) {
let mut balance = 0;
for amount in history {
balance += amount;
}
println!("{}: {} transactions, balance {}", owner, history.len(), balance);
}
fn deposit(history: &mut Vec<i64>, amount: i64) {
history.push(amount);
}
fn close_account(owner: String, history: Vec<i64>) -> String {
format!("closed {}'s account after {} transactions", owner, history.len())
}
fn main() {
let owner = String::from("Ferris");
let mut history = vec![100];
deposit(&mut history, 50);
deposit(&mut history, -30);
print_statement(&owner, &history);
let receipt = close_account(owner, history);
println!("{}", receipt);
}
Ferris: 3 transactions, balance 120
closed Ferris's account after 3 transactions
print_statementonly reads, so it borrows:mainkeeps both values.depositchanges the history, so it borrows mutably.close_accounttakes ownership on purpose: after closing, the account must not be used again, and now the compiler guarantees it. Any later use ofownerorhistoryis a compile error. Moves aren't only about memory; they also let a type say "this is finished".
Workplace notes
- In a typical Rust codebase, most parameters are
&T.&mut Tappears where a function's job is to change something, and owned parameters where the function stores or consumes the value. - Don't pass small
Copyvalues by reference:fn is_adult(age: &u32)just makes callers write&. (Clippy's pedantic lints flag it astrivially_copy_pass_by_ref.) - Clippy warns about
&Stringand&Vec<T>parameters (theptr_arglint). The next module, slices, shows why&strand&[T]are better parameter types. Everything you've learned here carries over unchanged. - The borrowing rules cost nothing at run time: references are plain pointers, and all checking happens at compile time.
Common mistakes
- Taking ownership "to be safe", then cloning at every call site. If you only read, borrow.
- Taking
&mutwhen&would do. It blocks other readers for no reason. - Returning references to new data (step 6). Return owned values.
Your task
Every function below takes ownership of its arguments, which forces main to .clone() everything. Refactor: give each function the least access it needs (& to read, &mut to change, plain u32s stay as they are), update the calls, and remove every .clone(). restock should change the vector in place and return nothing. The output must stay:
total: 215
bolts x120
nuts x80
gears x50
total: 250
Challenge 2
Fill in each function's parameter type: count_urgent only reads the task list, add changes it, and archive uses it up for good. The program should print:
urgent tasks: 1
archived 3 tasks
Challenge 3
Write transfer, which moves amount from one balance to another. If from holds less than amount, change nothing and return false; otherwise move the money and return true.
The borrowing rules make one mistake impossible: nobody can call transfer(&mut a, &mut a, 10), because that would be two &mut to the same balance (E0499).