Skip to content

Advanced Traits ​

Updated Mar 2026

Beyond basic trait implementation: associated types vs generics, default type parameters, operator overloading, fully qualified syntax, supertraits, and the newtype pattern.

Associated Types vs Generic Parameters ​

Both let you abstract over types, but they work differently:

rust
// ASSOCIATED TYPE: one implementation per type
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

// GENERIC PARAMETER: multiple implementations per type
pub trait ConvertTo<T> {
    fn convert(&self) -> T;
}

When to Use Which ​

FeatureAssociated TypesGenerics
Implementations per typeExactly oneMultiple
Caller annotation neededNoSometimes
Type inferenceBetterRequires hints
Use caseCore behavior (Iterator)Multiple conversions (From/Into)
rust
// Associated type: u32 can only iterate one way
impl Iterator for Counter {
    type Item = u32;  // fixed: Counter always yields u32
    fn next(&mut self) -> Option<u32> { /* ... */ }
}

// Generic: u32 can convert to multiple types
impl ConvertTo<f64> for u32 {
    fn convert(&self) -> f64 { *self as f64 }
}
impl ConvertTo<String> for u32 {
    fn convert(&self) -> String { self.to_string() }
}

Default Generic Type Parameters ​

Provide a default type so users don't have to specify it:

rust
// std::ops::Add definition:
pub trait Add<Rhs = Self> {  // default: Rhs is Self
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

This means you can implement Add without specifying Rhs:

rust
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

// Rhs defaults to Point (Self)
impl std::ops::Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

let p = Point { x: 1, y: 0 } + Point { x: 2, y: 3 };
assert_eq!(p, Point { x: 3, y: 3 });

Operator Overloading ​

Rust supports overloading operators by implementing traits from std::ops:

OperatorTraitMethod
+Add<Rhs>add(self, rhs)
-Sub<Rhs>sub(self, rhs)
*Mul<Rhs>mul(self, rhs)
/Div<Rhs>div(self, rhs)
%Rem<Rhs>rem(self, rhs)
- (unary)Negneg(self)
==PartialEqeq(&self, other)
< >PartialOrdpartial_cmp(&self, other)
[]Index<Idx>index(&self, index)
*xDerefderef(&self)

Cross-Type Addition ​

Override the default Rhs to add different types:

rust
struct Millimeters(u32);
struct Meters(u32);

impl std::ops::Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

let total = Millimeters(500) + Meters(2);
// total = Millimeters(2500)

Fully Qualified Syntax for Disambiguation ​

When a type implements multiple traits with same-named methods:

rust
trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) { println!("This is your captain speaking."); }
}

impl Wizard for Human {
    fn fly(&self) { println!("Up!"); }
}

impl Human {
    fn fly(&self) { println!("*waving arms furiously*"); }
}

Calling Specific Implementations ​

rust
let person = Human;

// Call the inherent method
person.fly();           // *waving arms furiously*

// Call trait methods explicitly
Pilot::fly(&person);    // This is your captain speaking.
Wizard::fly(&person);   // Up!

Fully Qualified Syntax for Associated Functions ​

For associated functions (no self parameter), use angle bracket syntax:

rust
trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String { String::from("Spot") }
}

impl Animal for Dog {
    fn baby_name() -> String { String::from("puppy") }
}

// Dog::baby_name() calls the inherent method
println!("{}", Dog::baby_name());              // Spot

// <Dog as Animal>::baby_name() calls the trait method
println!("{}", <Dog as Animal>::baby_name());  // puppy

General form: <Type as Trait>::function(receiver_if_method, args...)

Supertraits ​

A trait can require another trait as a prerequisite:

rust
use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();  // requires Display
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("* {} *", output);
        println!("{}", "*".repeat(len + 4));
    }
}

// Must implement Display to implement OutlinePrint
struct Point { x: i32, y: i32 }

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}

// Now Point has outline_print() available

Chained Supertraits ​

rust
trait A {}
trait B: A {}       // B requires A
trait C: A + B {}   // C requires both A and B

The Newtype Pattern ​

Circumvents the orphan rule by wrapping an external type in a local struct:

rust
// Can't impl Display for Vec<String> directly (orphan rule)
// But we CAN wrap it:

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("{}", w);  // [hello, world]

Zero-Cost Abstraction ​

The newtype wrapper is erased at compile time -- there is no runtime overhead. The compiled code is identical to using the inner type directly.

Implementing Deref for Ergonomics ​

rust
use std::ops::Deref;

struct Wrapper(Vec<String>);

impl Deref for Wrapper {
    type Target = Vec<String>;

    fn deref(&self) -> &Vec<String> {
        &self.0
    }
}

let w = Wrapper(vec![String::from("hello")]);
println!("Length: {}", w.len());  // Deref coercion: calls Vec::len

Newtype Use Cases ​

Use CaseExample
Implement external traits on external typesDisplay for Vec<T>
Type safety without runtime costMeters(f64) vs Seconds(f64)
Hiding implementation detailsPublic API uses wrapper, internal uses inner
Enforcing invariantsNonEmptyVec<T> that guarantees at least one element

Trait Objects and Dynamic Dispatch ​

rust
// Static dispatch: compiler generates specialized code (monomorphization)
fn notify(item: &impl Summary) {
    println!("{}", item.summarize());
}

// Dynamic dispatch: vtable lookup at runtime
fn notify_dyn(item: &dyn Summary) {
    println!("{}", item.summarize());
}

// Heterogeneous collection requires dynamic dispatch
let articles: Vec<Box<dyn Summary>> = vec![
    Box::new(NewsArticle { /* ... */ }),
    Box::new(Tweet { /* ... */ }),
];

Object Safety Rules ​

A trait is object-safe (can be used as dyn Trait) only if:

  1. Return type is not Self
  2. No generic type parameters on methods
rust
// Object-safe:
trait Draw {
    fn draw(&self);
}

// NOT object-safe (returns Self):
trait Clone {
    fn clone(&self) -> Self;
}

// NOT object-safe (generic method):
trait Serialize {
    fn serialize<W: std::io::Write>(&self, writer: &mut W);
}

Blanket Implementations ​

Implement a trait for all types that satisfy certain bounds:

rust
// From std: any type implementing Display also gets ToString
impl<T: fmt::Display> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}

// Now any Display type has .to_string()
let s = 42.to_string();  // works because i32: Display

Quick Reference ​

PatternSyntaxUse Case
Associated typetype Item;One impl per type
Default type paramtrait Add<Rhs=Self>Sensible defaults
Operator overloadimpl Add for TCustom +, -, etc.
Fully qualified<T as Trait>::method()Disambiguation
Supertraittrait A: BRequire prerequisite trait
Newtypestruct Wrap(Inner)Orphan rule workaround
Blanket implimpl<T: X> Y for TUniversal implementations

Built with VitePress | Rust SOP Documentation