Rust Traits Cheat Sheet
Explains how to define and implement traits, use trait bounds and generics, default methods, and trait objects for dynamic dispatch in Rust.
Defining & Implementing
Declaring a trait and implementing it for a type.
trait Summary { fn summarize(&self) -> String;}struct Article { title: String, body: String,}impl Summary for Article { fn summarize(&self) -> String { format!("{}: {}...", self.title, &self.body[..20.min(self.body.len())]) }}let a = Article { title: "Rust".into(), body: "Traits are powerful".into() };println!("{}", a.summarize());
Default Methods
Traits can provide implementations that types inherit for free.
trait Greet { fn name(&self) -> String; // Default implementation; can be overridden fn greet(&self) -> String { format!("Hello, {}!", self.name()) }}struct Person { name: String }impl Greet for Person { fn name(&self) -> String { self.name.clone() } // uses default greet()}
Trait Bounds
Constraining generic parameters to types that implement a trait.
// Trait bound with `impl Trait` syntaxfn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());}// Equivalent, explicit generic syntaxfn notify_generic<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize());}// Multiple bounds with `+`fn print_summary<T: Summary + std::fmt::Debug>(item: &T) { println!("{:?}", item);}// `where` clause for readability with many boundsfn complex<T, U>(t: &T, u: &U) -> Stringwhere T: Summary, U: Clone + std::fmt::Debug,{ t.summarize()}
Trait Objects
Dynamic dispatch for heterogeneous collections.
// dyn Trait enables dynamic dispatch; size not known at compile timefn notify_dyn(item: &dyn Summary) { println!("News: {}", item.summarize());}// A Vec of trait objects, boxed on the heaplet items: Vec<Box<dyn Summary>> = vec![ Box::new(Article { title: "A".into(), body: "First".into() }), Box::new(Article { title: "B".into(), body: "Second".into() }),];for item in &items { println!("{}", item.summarize());}
Common Derivable Traits
Traits the compiler can auto-implement with #[derive(...)].
- Debug- #[derive(Debug)] enables {:?} formatting for a type
- Clone- #[derive(Clone)] adds a .clone() method that deep-copies the value
- Copy- #[derive(Copy)] makes the type implicitly copied instead of moved (requires Clone)
- PartialEq / Eq- Enables == and != comparisons between instances
- PartialOrd / Ord- Enables <, >, and sorting via comparison
- Default- #[derive(Default)] provides a Default::default() constructor
- Hash- Allows the type to be used as a HashMap/HashSet key
Associated Types
A type placeholder tied to the implementation, avoiding an extra generic parameter.
trait Container { type Item; // associated type, one per impl fn get(&self, index: usize) -> Option<&Self::Item>; fn len(&self) -> usize;}struct Stack<T> { items: Vec<T> }impl<T> Container for Stack<T> { type Item = T; fn get(&self, index: usize) -> Option<&T> { self.items.get(index) } fn len(&self) -> usize { self.items.len() }}// Contrast with Iterator, the canonical associated-type trait:// trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }fn sum_lens<C: Container>(c: &C) -> usize { c.len()}
Supertraits
Requiring a type to already implement another trait before it can implement yours.
use std::fmt;// Any OutlinePrint implementor must also implement Display.trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); // available because of the Display bound let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("* {} *", output); println!("{}", "*".repeat(len + 4)); }}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 {} // Display is satisfied, so this compiles
Blanket Implementations
Implementing a trait for every type that satisfies a bound, as the standard library does for ToString.
trait Summary { fn summarize(&self) -> String;}trait Describe { fn describe(&self) -> String;}// Blanket impl: every type implementing Summary automatically gets Describe.impl<T: Summary> Describe for T { fn describe(&self) -> String { format!("[summary] {}", self.summarize()) }}// Standard library example this pattern mirrors:// impl<T: fmt::Display + ?Sized> ToString for T { ... }
Operator Overloading via std::ops
Implementing arithmetic traits lets your types work with built-in operators.
use std::ops::{Add, Index};#[derive(Debug, Clone, Copy, PartialEq)]struct Point { x: i32, y: i32 }impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}let p3 = Point { x: 1, y: 2 } + Point { x: 3, y: 4 }; // Point { x: 4, y: 6 }struct Row(Vec<i32>);impl Index<usize> for Row { type Output = i32; fn index(&self, i: usize) -> &i32 { &self.0[i] }}let r = Row(vec![10, 20, 30]);assert_eq!(r[1], 20); // uses the Index impl
Object Safety Rules for dyn Trait
A trait must satisfy these to be used as a trait object (Box<dyn Trait>, &dyn Trait).
- No generic methods- Methods can't have their own type parameters (fn foo<T>(&self)); the vtable can't hold every instantiation
- No Self by value returns/params (mostly)- Methods can't return Self or take Self by value; the concrete size is erased behind the trait object
- No associated constants- const items on the trait aren't representable in a vtable
- Self: Sized methods are excluded, not banned- Adding `where Self: Sized` to a specific method opts just that method out of the object-safety check
- Supertraits must also be object safe- If trait B: A, then dyn B requires A to be object safe too
- No static methods without a default- Associated functions that don't take self can't be dispatched dynamically
Use `impl Trait` in argument and return position for zero-cost static dispatch when the concrete type is known at compile time; reach for `Box<dyn Trait>` only when you need a heterogeneous collection or genuinely dynamic dispatch.