Rust Fundamentals: Ownership, Errors and Fearless Concurrency · Rust Basics and Ownership
Structs and Methods
Group related data in structs, add behaviour with impl blocks (methods with &self, &mut self or self, and associated functions like new), and derive Debug, Clone and PartialEq.
What you'll build
A bank account type with a constructor, methods to deposit and withdraw, a method that consumes the account to close it, and debug printing.
The idea
A struct groups named fields into one type:
struct Account { owner: String, balance_cents: i64 }
Behaviour goes in an impl block:
fn new(...) -> Self: an associated function (noself), called asAccount::new(...).newis a convention, not a keyword.fn balance(&self): a method that reads the struct.fn deposit(&mut self, ...): a method that changes it (the caller needs amutvariable).fn close(self): a method that takes ownership, so the value can't be used afterwards.
Derive common behaviour with attributes: #[derive(Debug, Clone, PartialEq)] gives {:?} printing, .clone() and ==.
Fields are private to the module by default when the struct is used from other modules; expose methods rather than fields to protect invariants (like "balance can't go negative").
The code
#[derive(Debug, Clone, PartialEq)]
struct Account {
owner: String,
balance_cents: i64,
}
impl Account {
fn new(owner: &str) -> Self {
Self { owner: owner.to_string(), balance_cents: 0 }
}
fn balance(&self) -> f64 {
self.balance_cents as f64 / 100.0
}
fn deposit(&mut self, cents: i64) {
self.balance_cents += cents;
}
fn withdraw(&mut self, cents: i64) -> bool {
if cents > self.balance_cents {
return false; // protect the invariant: no negative balance
}
self.balance_cents -= cents;
true
}
fn close(self) -> String {
format!("closed {}'s account, paid out {:.2}", self.owner, self.balance())
}
}
fn main() {
let mut acc = Account::new("Ana");
acc.deposit(12_500);
println!("balance: {:.2}", acc.balance());
println!("withdraw 200.00: {}", acc.withdraw(20_000));
println!("withdraw 30.00: {}", acc.withdraw(3_000));
let snapshot = acc.clone();
println!("{:?}", snapshot);
println!("same as snapshot: {}", acc == snapshot);
println!("{}", acc.close());
// acc.deposit(100); // error: `acc` was moved by close()
}
Output
balance: 125.00
withdraw 200.00: false
withdraw 30.00: true
Account { owner: "Ana", balance_cents: 9500 }
same as snapshot: true
closed Ana's account, paid out 95.00Run this on your computer — see “Get Started” for setup.
Common mistakes
- Forgetting
muton the variable before calling a&mut selfmethod. - Taking
selfby value by accident, which consumes the struct; use&selfunless consuming is the point. - Public fields everywhere. Methods let you enforce rules (no negative balance).
- Using floats for money. Store cents in an integer, as here.
- Missing derives.
{:?}needsDebug;==needsPartialEq.
Key points
structgroups fields;impladds methods and associated functions.&selfreads,&mut selfmodifies,selfconsumes.Self { ... }builds an instance;Type::new()is the constructor convention.#[derive(Debug, Clone, PartialEq)]adds common behaviour.
Further reading: Using structs, Method syntax.
Practice questions
What's the difference between &self, &mut self and self in a method?
Answer
&self borrows the value read-only; &mut self borrows it mutably so the method can change fields (the caller needs a mutable binding); self takes ownership, so the value can't be used after the call.
What is an associated function?
Answer
A function in an impl block that doesn't take self, called with Type::function() (e.g. constructors like Account::new).
Open in the app to mark lessons complete, take notes and track your progress.