Rust Traits & Generics Cheat Sheet
Quick-reference for trait definitions, generic bounds, trait objects, and derive macros. Each section includes copy-ready snippets with inline output comments.
Defining Traits
Traits define shared behavior. They are similar to interfaces in other languages.
trait Summary {
fn summarize(&self) -> String;
}trait Summary {
fn summarize(&self) -> String {
String::from("(read more...)")
}
}
// Implementors can override or use the defaulttrait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(by {})", self.summarize_author())
}
}Default methods can call other methods in the same trait, even required ones.
Implementing Traits
Use impl Trait for Type to provide the behavior. Every required method must be implemented.
struct Article { title: String, author: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
let a = Article { title: "Rust 2026".into(), author: "Alice".into() };
println!("{}", a.summarize()); // => "Rust 2026 by Alice"use std::fmt;
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)
}
}
println!("{}", Point { x: 1, y: 2 }); // => "(1, 2)"Derive Macros
Automatically implement common traits with #[derive(...)]. Works for types whose fields all implement the trait.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct User {
name: String,
age: u32,
}
let u = User { name: "Alice".into(), age: 30 };
println!("{:?}", u); // Debug
let u2 = u.clone(); // Clone
assert_eq!(u, u2); // PartialEq#[derive(Debug, Copy, Clone, PartialEq)]
struct Point { x: f64, y: f64 }
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // Copy: p1 is still valid
println!("{:?}", p1);Copy requires Clone. All fields must be Copy types (no String, Vec, etc.).
#[derive(Debug, Default)]
struct Config {
port: u16, // defaults to 0
verbose: bool, // defaults to false
name: String, // defaults to ""
}
let config = Config { port: 8080, ..Default::default() };
println!("{:?}", config);Trait Bounds
Constrain generic types to those implementing specific traits. No bound = no methods besides drop.
fn print_it<T: std::fmt::Display>(item: T) {
println!("{item}");
}fn log_it<T: std::fmt::Display + std::fmt::Debug>(item: T) {
println!("display: {item}, debug: {item:?}");
}fn process<T, U>(t: &T, u: &U) -> String
where
T: std::fmt::Display + Clone,
U: Clone + std::fmt::Debug,
{
format!("{t}")
}Inline bounds and where clauses are equivalent. Use where when bounds get long.
impl Trait
Shorthand in argument position (like a generic). Hides the concrete type in return position.
// These two are equivalent:
fn notify(item: &impl Summary) {
println!("{}", item.summarize());
}
fn notify_generic<T: Summary>(item: &T) {
println!("{}", item.summarize());
}fn make_iter() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}
for x in make_iter() {
println!("{x}");
}// ERROR: two different concrete types
// fn make_iter(asc: bool) -> impl Iterator<Item = i32> {
// if asc { vec![1,2,3].into_iter() }
// else { vec![3,2,1].into_iter().rev() } // different type!
// }
// FIX: use Box<dyn Iterator>
fn make_iter(asc: bool) -> Box<dyn Iterator<Item = i32>> {
if asc { Box::new(vec![1,2,3].into_iter()) }
else { Box::new(vec![3,2,1].into_iter().rev()) }
}Trait Objects (dyn Trait)
Dynamic dispatch via vtable. Use for heterogeneous collections or when the concrete type is not known at compile time.
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.radius.powi(2) }
}
struct Square { side: f64 }
impl Shape for Square {
fn area(&self) -> f64 { self.side.powi(2) }
}
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];fn print_area(shape: &dyn Shape) {
println!("area: {:.2}", shape.area());
}// Static (generics): monomorphized, zero cost, larger binary
fn area_static(s: &impl Shape) -> f64 { s.area() }
// Dynamic (trait object): vtable, flexible, slight overhead
fn area_dynamic(s: &dyn Shape) -> f64 { s.area() }Use generics when types are known at compile time. Use dyn Trait for heterogeneous collections.
Essential Std Traits: From, Into, Display, Debug
The most commonly implemented standard library traits. From/Into for conversion, Display/Debug for formatting.
// Implementing From gives you Into for free
impl From<&str> for Username {
fn from(s: &str) -> Self {
Username(s.to_string())
}
}
let u: Username = Username::from("alice");
let u: Username = "alice".into(); // Into is automaticimpl std::fmt::Display for Username {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "@{}", self.0)
}
}
println!("{}", username); // => "@alice"#[derive(Debug)]
struct Config { port: u16, host: String }
let c = Config { port: 8080, host: "localhost".into() };
println!("{:?}", c);
// => Config { port: 8080, host: "localhost" }
println!("{c:#?}"); // pretty-printedAssociated Types vs Generic Parameters
Associated types: one implementation per type. Generic parameters: multiple implementations per type.
trait Iterator {
type Item; // associated type
fn next(&mut self) -> Option<Self::Item>;
}
// Each type implements Iterator once with one Item type
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> { /* ... */ }
}use std::ops::Add;
// A type can implement Add multiple times for different Rhs
impl Add<f64> for Point {
type Output = Point;
fn add(self, rhs: f64) -> Point { /* ... */ }
}
impl Add<Point> for Point {
type Output = Point;
fn add(self, rhs: Point) -> Point { /* ... */ }
}Orphan Rule and Newtype Pattern
You cannot implement a foreign trait on a foreign type. The newtype pattern works around this.
// ERROR (E0117): Display and Vec are both from std
// impl std::fmt::Display for Vec<i32> { ... }struct Wrapper(Vec<String>);
impl std::fmt::Display for Wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
let w = Wrapper(vec!["a".into(), "b".into()]);
println!("{w}"); // => "[a, b]"The newtype IS yours, so you can implement any trait on it.
Turbofish and Disambiguation
The ::<> syntax helps the compiler when it cannot infer the type. UFCS disambiguates overlapping trait methods.
let n = "42".parse::<i32>().unwrap();
let v = vec![1, 2, 3].into_iter().collect::<Vec<_>>();trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
struct Human;
impl Pilot for Human { fn fly(&self) { println!("captain"); } }
impl Wizard for Human { fn fly(&self) { println!("up!"); } }
let h = Human;
Pilot::fly(&h); // => "captain"
Wizard::fly(&h); // => "up!"
// For associated functions (no self):
// <Type as Trait>::function()Object Safety
A trait can only be used as dyn Trait if all methods have a receiver and none return Self.
trait Draw {
fn draw(&self); // has receiver (&self) -- OK
fn name(&self) -> &str; // returns &str, not Self -- OK
}
let items: Vec<Box<dyn Draw>> = vec![/* ... */];trait Clonable {
fn clone(&self) -> Self; // returns Self -- NOT object-safe
}
// ERROR: cannot use dyn Clonable
// let items: Vec<Box<dyn Clonable>> = vec![];Clone is not object-safe. Use generics instead, or restructure the trait.
trait Animal {
fn speak(&self) -> &str; // object-safe
}
// Clone goes on a separate trait or uses where Self: Sized
trait AnimalClone: Animal {
fn clone_box(&self) -> Box<dyn Animal>
where
Self: Sized;
}Can you write this from memory?
Define a trait named `Greet` with a method `greet` that takes `&self` and returns nothing.