Traits

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