Step 2 of 6
Changing structs and passing them around
Like every variable in Rust, an instance is immutable unless you say mut. With let mut, you can change any field with =, += and friends. Mutability belongs to the whole instance: Rust has no way to mark just one field as mut.
Structs and functions
A struct follows the ownership rules you already know. Pass &Player to a function that only reads it, and &mut Player to a function that changes it. Through a reference you still write account.balance: Rust follows the reference for you, so there's no need for (*account).balance.
struct Account {
owner: String,
balance: i64,
}
fn deposit(account: &mut Account, amount: i64) {
account.balance += amount;
}
fn report(account: &Account) {
println!("{}: {}", account.owner, account.balance);
}
fn main() {
let mut acct = Account {
owner: String::from("Ada"),
balance: 100,
};
report(&acct);
deposit(&mut acct, 50);
acct.balance -= 30;
report(&acct);
}
Ada: 100
Ada: 120
depositborrows the account mutably, changes one field, and gives the borrow back when it returns.reportonly needs to look, so it takes&Account. Both can be called as often as you like, becausemainstill ownsacct.maincan change a field directly too (acct.balance -= 30), becauseacctismut.
Functions can also return a struct. That's how you'll usually build one: a function that takes a few values and fills in the rest.
struct Account {
owner: String,
balance: i64,
}
fn open_account(owner: &str) -> Account {
Account {
owner: owner.to_string(),
balance: 0,
}
}
fn main() {
let acct = open_account("Grace");
println!("{} starts with {}", acct.owner, acct.balance);
}
Grace starts with 0
Common mistakes
- Changing a field of an immutable instance:
struct Player {
name: String,
score: u32,
}
fn main() {
let p = Player { name: String::from("Ferris"), score: 10 };
p.score += 5;
println!("{} {}", p.name, p.score);
}
error[E0594]: cannot assign to `p.score`, as `p` is not declared as mutable
- Passing the struct by value when you meant to lend it. A struct with a
Stringinside moves into the function, just like theStringitself would. Ifreporttookaccount: Account, callingreport(acct)twice giveserror[E0382]: use of moved value: `acct`. Take&Accountinstead.
Your task
Write fn level_up(hero: &mut Hero). It adds 1 to level, adds 10 to max_hp, and then fully heals the hero: hp becomes the new max_hp. The name doesn't change.
Challenge 2
Write fn take_damage(hero: &mut Hero, amount: u32). It lowers hp by amount, but hp never goes below 0 (remember: subtracting past zero on a u32 panics in a debug build). No other field changes.
Challenge 3
Write fn transfer(from: &mut Account, to: &mut Account, amount: i64) from scratch. It moves amount from from's balance to to's balance. If from has less than amount, nothing changes at all. Owners never change.
Previous: Grouping data with a struct Next: Building structs with shorthand and update syntax