Skip to content

SOP: Trait Design ​

Updated Mar 2026

Overview ​

Traits are Rust's primary abstraction mechanism. This SOP covers designing traits, implementing them, using generics and bounds, choosing between static and dynamic dispatch, and advanced patterns like associated types and supertraits.

Trait Decision Tree ​

Defining Traits ​

rust
// Basic trait with required and default methods
pub trait Summary {
    // Required — implementors MUST provide
    fn summarize_author(&self) -> String;

    // Default implementation — can be overridden
    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

// Implementing for a type
struct Article {
    title: String,
    author: String,
    content: String,
}

impl Summary for Article {
    fn summarize_author(&self) -> String {
        self.author.clone()
    }

    fn summarize(&self) -> String {
        format!("{}, by {} — {}", self.title, self.author, &self.content[..50])
    }
}

Trait Bounds ​

rust
// Simple — impl Trait syntax
fn notify(item: &impl Summary) {
    println!("Breaking: {}", item.summarize());
}

// Generic — equivalent, more flexible
fn notify_generic<T: Summary>(item: &T) {
    println!("Breaking: {}", item.summarize());
}

// Multiple bounds
fn display_summary<T: Summary + std::fmt::Display>(item: &T) {
    println!("{}", item);
    println!("{}", item.summarize());
}

// Where clause for readability
fn complex_function<T, U>(t: &T, u: &U) -> String
where
    T: Summary + Clone,
    U: std::fmt::Debug + PartialEq,
{
    format!("{} / {:?}", t.summarize(), u)
}

Static vs Dynamic Dispatch ​

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

// Dynamic dispatch — trait object
fn print_any_summary(item: &dyn Summary) {
    println!("{}", item.summarize());
}

// Heterogeneous collection with trait objects
fn all_summaries() -> Vec<Box<dyn Summary>> {
    vec![
        Box::new(Article { title: "...".into(), author: "...".into(), content: "...".into() }),
        Box::new(Tweet { username: "...".into(), content: "...".into() }),
    ]
}

Associated Types vs Generic Parameters ​

rust
// Associated type — ONE implementation per type
trait Iterator {
    type Item;  // Implementor chooses the concrete type
    fn next(&mut self) -> Option<Self::Item>;
}

struct Counter { count: u32 }
impl Iterator for Counter {
    type Item = u32;  // Counter iterates over u32
    fn next(&mut self) -> Option<u32> {
        self.count += 1;
        if self.count <= 5 { Some(self.count) } else { None }
    }
}

// Generic parameter — MULTIPLE implementations per type
trait Convert<T> {
    fn convert(&self) -> T;
}

impl Convert<String> for i32 {
    fn convert(&self) -> String { self.to_string() }
}

impl Convert<f64> for i32 {
    fn convert(&self) -> f64 { *self as f64 }
}

Supertraits and Composition ​

rust
use std::fmt;

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

// Must implement Display first
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 {}

Object Safety Rules ​

A trait is object-safe (usable as dyn Trait) only if:

RuleExample
No Self in return positionfn clone(&self) -> Self is NOT object-safe
No generic type parameters on methodsfn process<T>(&self, item: T) is NOT object-safe
Methods that take self by value are excludedCan have them, but they won't be dispatchable
Associated types are finetype Item; is OK
rust
// Object-safe
trait Draw {
    fn draw(&self);
    fn area(&self) -> f64;
}

// NOT object-safe (returns Self)
trait Cloneable {
    fn clone_me(&self) -> Self;
}

Blanket Implementations ​

rust
// Implement ToString for anything that implements Display
// This is in std: impl<T: Display> ToString for T { ... }

// Your own blanket impl
trait Printable {
    fn print(&self);
}

impl<T: std::fmt::Debug> Printable for T {
    fn print(&self) {
        println!("{:?}", self);
    }
}

// Now EVERY Debug type gets print() for free

Checklist ​

  • [ ] Define required vs default methods clearly
  • [ ] Choose associated types vs generic params based on cardinality
  • [ ] Verify object safety if you need dyn Trait
  • [ ] Use where clauses for complex bounds
  • [ ] Consider supertraits for composing behavior
  • [ ] Document trait contracts (what implementors must guarantee)

Built with VitePress | Rust SOP Documentation