Rust Methods and impl
A struct describes what something is. Methods describe what it can do. A rectangle knows its width and height; it would be lovely if it could also tell us its area. In Rust you add methods to a struct inside an impl block, short for implementation.
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let r = Rectangle { width: 6.0, height: 4.0 };
println!("Area: {}", r.area());
println!("Square? {}", r.is_square());
}Area: 24 Square? false
A method is a function whose first parameter is self, the instance it was called on. &self means the method borrows the instance to read it. Inside, you reach the fields with self.width. You call a method with a dot: r.area(), and Rust passes r in as self for you.
Methods that change the instance
When a method needs to change the instance, it takes &mut self, and the variable must be mut:
struct BankAccount {
owner: String,
balance: i64,
}
impl BankAccount {
fn deposit(&mut self, amount: i64) {
self.balance += amount;
}
fn withdraw(&mut self, amount: i64) -> bool {
if amount > self.balance {
return false;
}
self.balance -= amount;
true
}
}
fn main() {
let mut account = BankAccount { owner: String::from("Ravi"), balance: 0 };
account.deposit(500);
println!("Withdraw 200: {}", account.withdraw(200));
println!("Withdraw 900: {}", account.withdraw(900));
println!("{} has {} rupees", account.owner, account.balance);
}Withdraw 200: true Withdraw 900: false Ravi has 300 rupees
Notice how the method protects the account: it refuses to take out more money than there is. Keeping rules like this inside methods means they are written in one place and cannot be forgotten somewhere else.
Associated functions and new
A function in an impl block that does not take self is called an associated function. It belongs to the type rather than to one instance, and you call it with two colons. The most common one is new, a friendly way to create an instance. You have already used one: String::new().
struct Timer {
name: String,
seconds: u32,
}
impl Timer {
fn new(name: &str) -> Timer {
Timer { name: name.to_string(), seconds: 0 }
}
fn tick(&mut self) {
self.seconds += 1;
}
fn report(&self) -> String {
format!("{} has run for {} s", self.name, self.seconds)
}
}
fn main() {
let mut t = Timer::new("Tea");
for _ in 0..3 {
t.tick();
}
println!("{}", t.report());
}Tea has run for 3 s
Inside an impl block you can also write Self instead of the type's name, as in fn new(name: &str) -> Self. Both mean the same thing.
Practice exercises
- Add a
perimetermethod toRectangleand print it. - Give
BankAccountanew(owner: &str)associated function that starts at a balance of 0. - Write a
Circlestruct with aradius, and methodsareaandscale(&mut self, factor: f64).