pub trait Greet {
// Required method — implementors must provide this
fn name(&self) -> &str;
// Default method — implementors may override
fn greeting(&self) -> String {
format!("Hello, {}!", self.name())
}
// Associated function (no self) — often used as constructors
fn kind() -> &'static str {
"greeter"
}
}
struct Person {
name: String,
}
impl Greet for Person {
fn name(&self) -> &str {
&self.name
}
// greeting() uses the default implementation
}
let p = Person { name: "Alice".into() };
println!("{}", p.greeting()); // "Hello, Alice!"
println!("{}", Person::kind()); // "greeter"
pub trait Encode {
type Output;
const VERSION: u8 = 1;
fn encode(&self) -> Self::Output;
}
struct Json(String);
impl Encode for Json {
type Output = Vec<u8>;
const VERSION: u8 = 2; // override default
fn encode(&self) -> Vec<u8> {
self.0.as_bytes().to_vec()
}
}
// Inline bound
fn print_item<T: std::fmt::Display>(item: T) {
println!("{}", item);
}
// Where clause — cleaner for multiple or complex bounds
fn log<T, E>(result: Result<T, E>)
where
T: std::fmt::Debug,
E: std::fmt::Display,
{
match result {
Ok(v) => println!("OK: {:?}", v),
Err(e) => eprintln!("ERR: {}", e),
}
}
// Multiple bounds with +
fn serialize_and_print<T: serde::Serialize + std::fmt::Debug>(val: &T) {
println!("{:?}", val);
let json = serde_json::to_string(val).unwrap();
println!("{}", json);
}
impl Trait in argument position is syntactic sugar for a generic parameter with that bound. Each call site can use a different concrete type.
// These are equivalent
fn process(item: impl std::fmt::Display) { println!("{}", item); }
fn process<T: std::fmt::Display>(item: T) { println!("{}", item); }
// impl Trait in return position — hides the concrete type
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
Note: impl Trait in return position always returns the same concrete type — it is not a trait object. Use Box<dyn Trait> when you need to return different types.
struct Wrapper<T: Clone> {
val: T,
}
// Bound on impl — methods only available when T: Clone + std::fmt::Debug
impl<T: Clone + std::fmt::Debug> Wrapper<T> {
fn inspect(&self) -> T {
println!("{:?}", self.val);
self.val.clone()
}
}
Use associated types when there is only one sensible implementation per type. Iterator is the canonical example — a type can only produce one kind of item.
// Associated type: Vec<i32> implements Iterator<Item = &i32>
// There is exactly one Item type per implementor
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
// Caller syntax is clean
fn sum_iter<I: Iterator<Item = i32>>(mut it: I) -> i32 {
let mut total = 0;
while let Some(n) = it.next() { total += n; }
total
}
Use generic parameters when a type may implement the trait for many different type arguments. From<T> is the canonical example — String implements From<&str>, From<char>, etc.
// Generic parameter: String can implement Converter for many T
trait Converter<T> {
fn convert(&self) -> T;
}
struct Celsius(f64);
impl Converter<f64> for Celsius {
fn convert(&self) -> f64 { self.0 }
}
impl Converter<String> for Celsius {
fn convert(&self) -> String { format!("{}°C", self.0) }
}
let c = Celsius(100.0);
let f: f64 = c.convert();
let s: String = c.convert();
A supertrait is a trait that must be implemented before another trait can be implemented. Declare it with : after the trait name.
use std::fmt;
// Animal requires Display and Debug
trait Animal: fmt::Display + fmt::Debug {
fn sound(&self) -> &str;
}
#[derive(Debug)]
struct Dog;
impl fmt::Display for Dog {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dog")
}
}
impl Animal for Dog {
fn sound(&self) -> &str { "woof" }
}
You can use a trait object of the supertrait when you only need shared functionality.
fn describe(animal: &dyn fmt::Display) {
println!("{}", animal);
}
let dog = Dog;
describe(&dog as &dyn fmt::Display);
A trait object (dyn Trait) is a fat pointer: a data pointer plus a vtable pointer. They enable dynamic dispatch.
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
}
impl Shape for Square {
fn area(&self) -> f64 { self.side * self.side }
}
// Heterogeneous collection via Box<dyn Trait>
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
for shape in &shapes {
println!("area = {:.2}", shape.area());
}
A trait is object-safe (usable as dyn Trait) if:
Self&self, &mut self, or Box<Self>)// NOT object-safe: clone() returns Self
// trait Cloneable: Clone {} // cannot be dyn
// Object-safe version: return Box<dyn Trait>
trait DynClone {
fn clone_box(&self) -> Box<dyn DynClone>;
}
// NOT object-safe: generic method
trait Bad {
fn convert<T>(&self) -> T; // generic method — not dispatchable
}
// OK: use associated type instead
trait Good {
type Output;
fn convert(&self) -> Self::Output;
}
// Sendable trait object
fn spawn_worker(task: Box<dyn Fn() + Send + 'static>) {
std::thread::spawn(task);
}
// Arc<dyn Trait + Send + Sync> for shared access across threads
use std::sync::Arc;
let shared: Arc<dyn Shape + Send + Sync> = Arc::new(Circle { radius: 1.0 });
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
name: String,
value: u32,
}
// Debug: {:?} and {:#?} formatting
// Clone: .clone() method
// PartialEq/Eq: == and != operators
// Hash: usable as HashMap/HashSet key (requires PartialEq + Eq)
// Default: Config::default() returns Config { name: "", value: 0 }
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Version(u32, u32, u32);
// PartialOrd/Ord: <, >, <=, >= operators; enables .sort() on Vec<Version>
// Ord requires PartialOrd; PartialOrd requires PartialEq
derive_more provides derives for common trait impls that the standard library does not include.
// Cargo.toml: derive_more = { version = "1", features = ["display", "from", "into"] }
use derive_more::{Display, From, Into};
#[derive(Display, From, Into)]
#[display("User({name}, {id})")]
struct UserId {
name: String,
id: u64,
}
let id = UserId::from(("Alice".to_string(), 42u64));
let (name, num): (String, u64) = id.into();
use std::fmt;
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:.2}, {:.2})", self.x, self.y)
}
}
// Implementing Display gives .to_string() for free via blanket impl
let p = Point { x: 1.0, y: 2.0 };
println!("{}", p);
let s: String = p.to_string();
Implement From; Into is derived automatically via a blanket impl.
struct Meters(f64);
struct Feet(f64);
impl From<Meters> for Feet {
fn from(m: Meters) -> Self {
Feet(m.0 * 3.28084)
}
}
let m = Meters(1.0);
let f: Feet = m.into(); // Into<Feet> for Meters — derived from From
let f2 = Feet::from(Meters(2.0)); // From<Meters> for Feet
use std::str::FromStr;
#[derive(Debug)]
struct Color { r: u8, g: u8, b: u8 }
#[derive(Debug)]
struct ParseColorError(String);
impl std::fmt::Display for ParseColorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "parse color error: {}", self.0)
}
}
impl FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 3 {
return Err(ParseColorError("expected R,G,B".into()));
}
let parse = |p: &str| p.trim().parse::<u8>()
.map_err(|e| ParseColorError(e.to_string()));
Ok(Color { r: parse(parts[0])?, g: parse(parts[1])?, b: parse(parts[2])? })
}
}
let c: Color = "255, 128, 0".parse().unwrap();
Deref enables the * operator and auto-deref coercions. Use it for smart-pointer-like types, not general type conversions.
use std::ops::{Deref, DerefMut};
struct Wrapper<T>(Vec<T>);
impl<T> Deref for Wrapper<T> {
type Target = Vec<T>;
fn deref(&self) -> &Vec<T> { &self.0 }
}
impl<T> DerefMut for Wrapper<T> {
fn deref_mut(&mut self) -> &mut Vec<T> { &mut self.0 }
}
let mut w = Wrapper(vec![1, 2, 3]);
w.push(4); // DerefMut: Vec::push via auto-deref
println!("{}", w.len()); // Deref: Vec::len via auto-deref
AsRef<T> is for cheap reference conversions. Prefer it over Deref in function parameters.
// Accept String, &str, PathBuf, &Path, etc. — anything that is AsRef<str>
fn print_upper(s: impl AsRef<str>) {
println!("{}", s.as_ref().to_uppercase());
}
print_upper("hello");
print_upper(String::from("world"));
// AsRef<Path> for filesystem functions
fn read_config(path: impl AsRef<std::path::Path>) -> std::io::Result<String> {
std::fs::read_to_string(path)
}
The sealed trait pattern restricts who can implement a trait — useful for stable API surfaces in libraries.
// In your library crate
mod private {
pub trait Sealed {}
}
pub trait MyTrait: private::Sealed {
fn do_thing(&self);
}
// Implement Sealed only for types you control
pub struct TypeA;
pub struct TypeB;
impl private::Sealed for TypeA {}
impl private::Sealed for TypeB {}
impl MyTrait for TypeA {
fn do_thing(&self) { println!("A"); }
}
impl MyTrait for TypeB {
fn do_thing(&self) { println!("B"); }
}
// External users CANNOT implement MyTrait because they cannot implement
// private::Sealed (it's not publicly accessible)
Extension traits let you add methods to types you do not own, including primitives and standard library types.
pub trait StrExt {
fn word_count(&self) -> usize;
fn capitalize(&self) -> String;
}
impl StrExt for str {
fn word_count(&self) -> usize {
self.split_whitespace().count()
}
fn capitalize(&self) -> String {
let mut chars = self.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
}
// Bring the trait into scope to use the methods
use crate::StrExt;
println!("{}", "hello world".word_count()); // 2
println!("{}", "hello".capitalize()); // "Hello"
Extension traits must be in scope (imported) to use their methods. This is why use std::io::Write and similar imports are necessary.
// Generic struct
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn push(&mut self, item: T) { self.items.push(item); }
fn pop(&mut self) -> Option<T> { self.items.pop() }
fn is_empty(&self) -> bool { self.items.is_empty() }
}
// Generic enum
enum Either<L, R> {
Left(L),
Right(R),
}
Const generics allow types to be parameterized by constant values (integers, booleans, chars).
// Array type parameterized by size — zero-cost abstraction
struct Matrix<T, const ROWS: usize, const COLS: usize> {
data: [[T; COLS]; ROWS],
}
impl<T: Default + Copy, const R: usize, const C: usize> Matrix<T, R, C> {
fn new() -> Self {
Matrix { data: [[T::default(); C]; R] }
}
fn rows(&self) -> usize { R }
fn cols(&self) -> usize { C }
}
let m: Matrix<f64, 3, 4> = Matrix::new();
assert_eq!(m.rows(), 3);
PhantomData<T> tells the compiler that a type logically contains T without storing it, affecting variance and drop checking.
use std::marker::PhantomData;
// A typed ID that cannot be mixed between entity types
struct Id<T> {
value: u64,
_phantom: PhantomData<T>,
}
impl<T> Id<T> {
fn new(value: u64) -> Self {
Id { value, _phantom: PhantomData }
}
}
struct User;
struct Order;
let user_id: Id<User> = Id::new(1);
let order_id: Id<Order> = Id::new(1);
// Cannot mix: user_id and order_id are different types even though value is same
When the compiler cannot infer a generic type argument, use ::<> (turbofish) to supply it explicitly.
// Collect requires knowing what to collect into
let nums: Vec<i32> = "1 2 3".split(' ')
.map(|s| s.parse::<i32>().unwrap()) // turbofish on parse
.collect::<Vec<_>>(); // turbofish on collect (alternative to type annotation)
// Any generic function may need turbofish
fn identity<T>(val: T) -> T { val }
let x = identity::<String>(String::from("hello"));
T: 'a means "all references inside T live at least as long as 'a". This is required when storing generic types behind references.
struct Holder<'a, T: 'a> {
reference: &'a T,
}
// 'static bound: T contains no non-static references
// (common for thread-spawning and stored callbacks)
fn store_callback<F: Fn() + Send + 'static>(f: F) {
std::thread::spawn(f);
}
A blanket implementation applies a trait to any type that satisfies certain bounds, rather than to a specific named type.
// From the standard library — any T that implements Display also gets ToString
impl<T: std::fmt::Display> ToString for T {
fn to_string(&self) -> String {
format!("{}", self)
}
}
// This is why any Display type has .to_string() for free
42i32.to_string();
3.14f64.to_string();
trait Summary {
fn summarize(&self) -> String;
}
// Any type that implements Display also gets a free Summary impl
impl<T: std::fmt::Display> Summary for T {
fn summarize(&self) -> String {
format!("Summary: {}", self)
}
}
Be careful: blanket impls can create conflicts. Two blanket impls that could overlap will fail to compile (the orphan rule plus coherence checking prevents ambiguity).
You can implement a trait for a type only if either the trait or the type is defined in your crate. Both cannot be foreign.
// OK: MyTrait (yours) for String (foreign)
impl MyTrait for String { ... }
// OK: Display (foreign) for MyType (yours)
impl std::fmt::Display for MyType { ... }
// ERROR: Display (foreign) for Vec<T> (foreign) — orphan rule violation
// impl std::fmt::Display for Vec<i32> { ... }
Work around this using the newtype pattern:
struct MyVec(Vec<i32>);
impl std::fmt::Display for MyVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}