Rust Cheatsheet
Traits
Use this Rust reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Defining Traits
trait Summary { // Required method (no default body) fn summarize_author(&self) -> String; // Default method (can be overridden) fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) } }
Implementing Traits
struct Article { title: String, author: String, content: String, } impl Summary for Article { fn summarize_author(&self) -> String { self.author.clone() } // Override default fn summarize(&self) -> String { format!("{}, by {} — {}", self.title, self.author, &self.content[..20]) } } struct Tweet { username: String, content: String } impl Summary for Tweet { fn summarize_author(&self) -> String { format!("@{}", self.username) } // summarize() uses default implementation } let article = Article { title: "Rust is great".to_string(), author: "Alice".to_string(), content: "Long content here...".to_string(), }; println!("{}", article.summarize()); println!("{}", Tweet { username: "bob".to_string(), content: "hi".to_string() }.summarize());
Trait Bounds
Constrain generic types to those that implement specific traits.
// Syntax 1: angle bracket bound fn notify<T: Summary>(item: &T) { println!("{}", item.summarize()); } // Syntax 2: impl Trait (more concise for simple cases) fn notify(item: &impl Summary) { println!("{}", item.summarize()); } // Multiple bounds fn notify<T: Summary + std::fmt::Display>(item: &T) { println!("{} — {}", item, item.summarize()); } // where clause — cleaner for complex bounds fn complex<T, U>(t: T, u: U) where T: std::fmt::Display + Clone, U: Summary + std::fmt::Debug, { println!("{} {:?}", t, u.summarize()); }
Returning Traits (impl Trait / dyn Trait)
// impl Trait — static dispatch, opaque type (concrete type unknown to caller) fn make_summary(use_article: bool) -> impl Summary { // All arms must return the SAME concrete type Article { title: "default".to_string(), author: "unknown".to_string(), content: "...".to_string(), } } // Box<dyn Trait> — dynamic dispatch, can return different types fn make_summary_dyn(use_article: bool) -> Box<dyn Summary> { if use_article { Box::new(Article { title: "t".to_string(), author: "a".to_string(), content: "c".to_string() }) } else { Box::new(Tweet { username: "bob".to_string(), content: "hi".to_string() }) } }
Dynamic Dispatch: dyn Trait
// Trait objects — runtime polymorphism let items: Vec<Box<dyn Summary>> = vec![ Box::new(Article { title: "t".to_string(), author: "a".to_string(), content: "c".to_string() }), Box::new(Tweet { username: "bob".to_string(), content: "hi".to_string() }), ]; for item in &items { println!("{}", item.summarize()); // virtual dispatch } // Without Box (reference trait object) fn print_summary(item: &dyn Summary) { println!("{}", item.summarize()); } // Static dispatch (monomorphization, faster, larger binary) fn print_summary_static<T: Summary>(item: &T) { println!("{}", item.summarize()); }
Object Safety: A trait is object-safe (usable as dyn Trait) if:
- All methods have a self receiver.
- Methods don't use generic type parameters.
- Return type is not Self.
Standard Library Traits
Display and Debug
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, "({}, {})", self.x, self.y) } } impl fmt::Debug for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Point") .field("x", &self.x) .field("y", &self.y) .finish() } }
Clone and Copy
#[derive(Clone)] struct Config { name: String } impl Clone for Config { fn clone(&self) -> Self { Config { name: self.name.clone() } } } // Copy (for stack-only, cheap-to-copy types) #[derive(Clone, Copy)] struct Pair(i32, i32);
From and Into
struct Wrapper(i32); impl From<i32> for Wrapper { fn from(val: i32) -> Wrapper { Wrapper(val) } } // From impl gives Into for free let w = Wrapper::from(42); let w: Wrapper = 42.into(); // Into::into // String conversions let s = String::from("hello"); let n: i32 = "42".parse().unwrap(); let f = f64::from(42i32);
Iterator
struct Counter { count: u32, max: u32 } impl Counter { fn new(max: u32) -> Self { Counter { count: 0, max } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<u32> { if self.count < self.max { self.count += 1; Some(self.count) } else { None } } } // All iterator adapter methods (map, filter, sum, ...) come for free! let sum: u32 = Counter::new(5).sum(); // 15 let pairs: Vec<_> = Counter::new(3).zip(Counter::new(3)).collect();
Operator Traits (std::ops)
| Trait | Operator | Example |
|---|---|---|
Add | + | a + b |
Sub | - | a - b |
Mul | * | a * b |
Div | / | a / b |
Rem | % | a % b |
Neg | unary - | -a |
Not | ! | !a |
BitAnd | & | a & b |
BitOr | | | a | b |
BitXor | ^ | a ^ b |
Shl | << | a << b |
Shr | >> | a >> b |
AddAssign | += | a += b |
Index | [] | a[i] |
IndexMut | []= | a[i] = v |
Deref | * | *a |
DerefMut | * (mut) | *a = v |
Fn, FnMut, FnOnce | () | f(x) |
Drop | (automatic) | out of scope |
use std::ops::{Add, Index}; #[derive(Debug, Clone, Copy)] struct Vec2 { x: f64, y: f64 } impl Add for Vec2 { type Output = Vec2; fn add(self, other: Vec2) -> Vec2 { Vec2 { x: self.x + other.x, y: self.y + other.y } } }
PartialEq, Eq, PartialOrd, Ord
#[derive(Debug)] struct Score(i32); impl PartialEq for Score { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for Score {} // marker trait; no extra methods needed impl PartialOrd for Score { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for Score { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) } }
Default
#[derive(Default, Debug)] struct Config { width: u32, // defaults to 0 height: u32, title: String, // defaults to "" } let c = Config::default(); // or let c: Config = Default::default(); // Custom Default struct Server { host: String, port: u16 } impl Default for Server { fn default() -> Self { Server { host: "localhost".to_string(), port: 8080 } } }
Deref and DerefMut
use std::ops::{Deref, DerefMut}; struct MyBox<T>(T); impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } impl<T> DerefMut for MyBox<T> { fn deref_mut(&mut self) -> &mut T { &mut self.0 } } let x = MyBox(5); assert_eq!(5, *x); // *x calls *(x.deref()) // Deref coercion: &MyBox<String> → &String → &str automatically
Hash
use std::hash::{Hash, Hasher}; struct Key { id: u32, name: String } impl Hash for Key { fn hash<H: Hasher>(&self, state: &mut H) { self.id.hash(state); self.name.hash(state); } } // (Also implement PartialEq + Eq for use as HashMap key)
Trait Associated Types
trait Container { type Item; // associated type fn first(&self) -> Option<&Self::Item>; fn last(&self) -> Option<&Self::Item>; fn len(&self) -> usize; fn is_empty(&self) -> bool { self.len() == 0 } } struct Stack<T> { data: Vec<T> } impl<T> Container for Stack<T> { type Item = T; fn first(&self) -> Option<&T> { self.data.first() } fn last(&self) -> Option<&T> { self.data.last() } fn len(&self) -> usize { self.data.len() } }
Associated types vs generics: use associated types when there should be only one implementation per type (like Iterator::Item), generics when multiple implementations make sense.
Supertraits
use std::fmt; // OutlinePrint requires Display to already be implemented trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); // can use Display methods let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("* {} *", output); println!("{}", "*".repeat(len + 4)); } } struct Point { x: f64, y: f64 } impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl OutlinePrint for Point {} // outline_print comes for free
Blanket Implementations
// Implement a trait for ALL types that satisfy a bound // (This is how std does it, e.g. ToString for all Display types) impl<T: fmt::Display> ToString for T { fn to_string(&self) -> String { format!("{}", self) } } // Your own blanket impl: trait Printable: fmt::Display { fn print(&self) { println!("{}", self); } } impl<T: fmt::Display> Printable for T {} 42.print(); // works for any Display type "hello".print(); 3.14f64.print();
Trait Objects and Sized
// Trait objects are unsized — must be behind a pointer fn takes_trait(x: &dyn Summary) {} // ok fn takes_boxed(x: Box<dyn Summary>) {} // ok fn takes_rc(x: std::rc::Rc<dyn Summary>) {} // ok // ?Sized — allow unsized types fn generic_fn<T: ?Sized>(x: &T) {} // works for both sized and unsized T