Why this matters
A struct describes data. Most data comes with operations: a rectangle has an area, an account can be deposited into, a counter can tick. Methods attach functions to a type, so related code lives together and calls read naturally: account.deposit(100) instead of deposit(&mut account, 100). In Rust, a method's first parameter also states, in the signature, what it does with ownership: read, modify, or consume. That makes methods a direct application of everything from modules 4 and 5.
impl blocks and self
#[derive(Debug)]
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rect) -> bool {
self.width >= other.width && self.height >= other.height
}
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
}
fn main() {
let mut big = Rect { width: 10, height: 8 };
let small = Rect { width: 3, height: 4 };
println!("area {}", big.area());
println!("holds small: {}", big.can_hold(&small));
big.scale(2);
println!("{:?}", big);
}
area 80
holds small: true
Rect { width: 20, height: 16 }
impl Rect { ... }contains functions associated withRect.- A method has a first parameter named
self.&selfis short forself: &Rect: the method borrows the value it is called on. big.area()calls the method; Rust passes&bigasselfautomatically.- Inside, fields are reached through
self.width.
The three receivers
The form of self is an ownership decision, just like any parameter:
| receiver | means | caller afterwards | typical use |
|---|---|---|---|
&self | borrow to read | still owns, unchanged | getters, queries, computations |
&mut self | borrow to modify | still owns, changed | setters, updates |
self | take ownership | value is moved, gone | conversions, finishing a builder |
struct Draft {
text: String,
}
struct Post {
text: String,
}
impl Draft {
fn add(&mut self, s: &str) {
self.text.push_str(s);
}
fn word_count(&self) -> usize {
self.text.split_whitespace().count()
}
fn publish(self) -> Post {
Post { text: self.text }
}
}
fn main() {
let mut d = Draft { text: String::new() };
d.add("Hello ");
d.add("methods");
println!("{} words", d.word_count());
let p = d.publish();
println!("published: {}", p.text);
}
2 words
published: Hello methods
publish(self) moves the draft into the method, which moves its String into the new Post, with no copying. After it, d is gone: try d.add("more") and you get E0382 with the note "Draft::publish takes ownership of the receiver self, which moves d". The type system now models the workflow: a published post can no longer be edited as a draft.
Automatic referencing
When you call big.area(), Rust adds &, &mut or * as needed to match the method's receiver. So big.area() means Rect::area(&big), and big.scale(2) means Rect::scale(&mut big, 2). That is why method calls never need (&big).area(). You can write the fully qualified form too, which shows that methods are just functions:
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let r = Rect { width: 2, height: 5 };
let by_method = r.area();
let by_function = Rect::area(&r);
println!("{by_method} {by_function}");
}
10 10
The borrowing rules apply unchanged: calling a &mut self method while holding a reference into the struct is E0502, and calling one on an immutable binding is E0596.
Associated functions and constructors
Functions in an impl block without self are associated functions, called with Type::name(). String::from and String::new are examples. The conventional constructor is named new:
#[derive(Debug)]
struct Temperature {
celsius: f64,
}
impl Temperature {
fn new(celsius: f64) -> Self {
Self { celsius }
}
fn from_fahrenheit(f: f64) -> Self {
Self::new((f - 32.0) * 5.0 / 9.0)
}
fn fahrenheit(&self) -> f64 {
self.celsius * 9.0 / 5.0 + 32.0
}
}
fn main() {
let t = Temperature::from_fahrenheit(212.0);
println!("{:?} = {} F", t, t.fahrenheit());
}
Temperature { celsius: 100.0 } = 212 F
Self (capital S) inside an impl block is an alias for the type being implemented, here Temperature. Rust has no special constructor syntax; new is just a convention, and you can offer several named constructors like from_fahrenheit. Constructors are a good place to enforce rules (for example, rejecting a negative width), which pairs with privacy in module 12 so that nobody can bypass them.
Methods and field names
A method may have the same name as a field: fn width(&self) -> u32 { self.width }. r.width is the field, r.width() the method. This is the usual way to offer read-only access to private fields (module 12).
You can split an impl into several blocks; it makes no difference to the compiler. Later you will write impl SomeTrait for Type blocks too (module 10).
Common mistakes
Forgetting self: a function in an impl without self cannot be called with dot syntax:
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn area(r: &Rect) -> u32 {
r.width * r.height
}
}
fn main() {
let r = Rect { width: 2, height: 3 };
println!("{}", r.area());
}
error[E0599]: no method named `area` found for struct `Rect` in the current scope
--> main.rs:14:22
|
1 | struct Rect {
| ----------- method `area` not found for this struct
...
14 | println!("{}", r.area());
| --^^^^--
| | |
| | this is an associated function, not a method
| help: use associated function syntax instead: `Rect::area(&r)`
|
= note: found the following associated functions; to be used as methods, functions must have a `self` parameter
note: the candidate is defined in an impl for the type `Rect`
--> main.rs:7:5
|
7 | fn area(r: &Rect) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^
E0599: "this is an associated function, not a method". Make the first parameter &self.
Calling a &mut self method on an immutable binding: E0596 "cannot borrow c as mutable". Declare it let mut.
Writing width instead of self.width inside a method: E0425 cannot find value width, with a help suggesting self.width.
Taking self when &self would do: the value is consumed by a mere query. Use self only when consuming is the point.
Recap
impl Type { ... }holds methods and associated functions for a type.&selfreads,&mut selfmodifies,selfconsumes; the choice is visible in the signature.value.method()auto-borrows as needed; it is sugar forType::method(&value).- Associated functions (no
self) are called asType::name();newis the conventional constructor. Selfnames the type inside itsimpl.
Going further: add a fn square(size: u32) -> Self constructor to Rect, and a method fn into_tuple(self) -> (u32, u32). Then read the Rust Book chapter 5.3, "Method Syntax".