Trait Methods
Trait methods are functions declared inside a trait block whose first parameter is a receiver (&self, &mut self, self, or self: Box<Self>), enabling method-call syntax (receiver.method(...)) alongside associated functions that take no receiver (Type::func(...)).
Receiver Types in Trait Methods
The receiver parameter dictates how a trait method accesses or consumes the implementing value:
| Receiver Signature | Desugared Type | Ownership / Borrowing Semantics |
|---|---|---|
&self |
self: &Self |
Borrows the instance immutably; most common for queries and formatting |
&mut self |
self: &mut Self |
Borrows the instance mutably; used for state updates (Iterator::next) |
self |
self: Self |
Takes ownership and consumes the instance (requires Self: Sized) |
self: Box<Self> |
self: Box<Self> |
Consumes a heap-allocated Box<Self> (works even on Box<dyn Trait>) |
(no self) |
Associated function | Called via Type::function(); used for constructors (Default::default()) |
trait Lifecycle {
fn init(name: &str) -> Self;
fn status(&self) -> &str;
fn advance(&mut self);
fn into_report(self) -> String;
}
struct Task {
name: String,
step: u32,
}
impl Lifecycle for Task {
fn init(name: &str) -> Self {
Self {
name: name.to_owned(),
step: 0,
}
}
fn status(&self) -> &str {
&self.name
}
fn advance(&mut self) {
self.step += 1;
}
fn into_report(self) -> String {
format!("Task '{}' finished after {} steps", self.name, self.step)
}
}
fn main() {
let mut task = Task::init("indexer");
println!("Running: {}", task.status());
task.advance();
task.advance();
println!("{}", task.into_report());
}
Running: indexer
Task 'indexer' finished after 2 steps
Method Resolution Order and Auto-Ref / Auto-Deref
When you write value.method(), the compiler resolves the receiver through a deterministic sequence of steps:
Receiver T
|-- 1. Check inherent methods on T, &T, &mut T
|-- 2. Check visible trait methods on T, &T, &mut T
|-- 3. Deref T -> *T (via Deref / DerefMut) and repeat steps 1 & 2
Because inherent methods are checked before trait methods at the same deref level, if a type defines both an inherent method fn label(&self) and implements a trait Titled with fn label(&self), calling item.label() always invokes the inherent method.
Calling Trait Methods with Fully Qualified Syntax
Every trait method can also be called as a regular function on the trait itself by passing the receiver as the first argument (Trait::method(&value) or <Type as Trait>::method(&value)):
trait Render {
fn render(&self) -> String;
}
struct Button {
text: &'static str,
}
impl Button {
fn render(&self) -> String {
format!("[Inherent: {}]", self.text)
}
}
impl Render for Button {
fn render(&self) -> String {
format!("<button>{}</button>", self.text)
}
}
fn main() {
let btn = Button { text: "Submit" };
// 1. Dot syntax resolves to the inherent method first:
println!("Dot call: {}", btn.render());
// 2. Trait-qualified syntax calls the Render trait method:
println!("Trait call: {}", Render::render(&btn));
// 3. Fully qualified syntax specifies both concrete type and trait:
println!("Fully qualified: {}", <Button as Render>::render(&btn));
}
Dot call: [Inherent: Submit]
Trait call: <button>Submit</button>
Fully qualified: <button>Submit</button>
Generic Methods Inside a Trait
A trait method can declare its own generic type parameters (fn write_value<V: Display>(&mut self, val: V)):
use std::fmt::Display;
trait Logger {
fn log_kv<K: Display, V: Display>(&self, key: K, value: V);
}
struct ConsoleLogger;
impl Logger for ConsoleLogger {
fn log_kv<K: Display, V: Display>(&self, key: K, value: V) {
println!("[LOG] {key}: {value}");
}
}
fn main() {
let logger = ConsoleLogger;
logger.log_kv("status", 200);
logger.log_kv("latency_ms", 4.75);
}
[LOG] status: 200
[LOG] latency_ms: 4.75