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)

TraitOperatorExample
Add+a + b
Sub-a - b
Mul*a * b
Div/a / b
Rem%a % b
Negunary --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