Skip to content
Unverified — AI-generated content. Help verify this page

Rust Cheat Sheet

Quick reference for Rust ownership, borrowing, lifetimes, common types, pattern matching, iterators, traits, generics, and Cargo commands.


Ownership, Borrowing & Lifetimes

Ownership Rules

  1. Each value has exactly one owner
  2. When the owner goes out of scope, the value is dropped
  3. Ownership can be moved or borrowed
rust
let s1 = String::from("hello");
let s2 = s1;                     // s1 is MOVED, no longer valid
// println!("{s1}");              // Error: value used after move

let s3 = s2.clone();             // Deep copy, both valid

Borrowing

rust
fn length(s: &String) -> usize { // Immutable borrow
    s.len()
}

fn push_char(s: &mut String) {   // Mutable borrow
    s.push('!');
}

let mut name = String::from("Alice");
let len = length(&name);         // Immutable borrow
push_char(&mut name);            // Mutable borrow

Borrowing Rules

RuleAllowedNot Allowed
Multiple &Tlet a = &x; let b = &x;--
Single &mut Tlet a = &mut x;let b = &mut x; (second)
Mix &T and &mut T--let a = &x; let b = &mut x;

Lifetimes

rust
// Explicit lifetime annotations
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}

// Lifetime in structs
struct Excerpt<'a> {
    text: &'a str,
}

// Static lifetime (lives for entire program)
let s: &'static str = "lives forever";

TIP

The compiler's lifetime elision rules handle most cases. Only add explicit lifetimes when the compiler asks.


Common Types

String vs &str

rust
let s: &str = "hello";           // String slice (borrowed, immutable)
let s: String = String::from("hello"); // Owned, heap-allocated, growable

// Conversions
let owned: String = "hello".to_string();
let borrowed: &str = &owned;

Vec

rust
let mut v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];           // Macro shorthand

v.push(4);                       // Append
v.pop();                         // Remove last -> Option<T>
v.len();                         // Length
v.is_empty();                    // Check empty
v.contains(&3);                  // Search
v.iter();                        // Immutable iterator
v.iter_mut();                    // Mutable iterator
v.into_iter();                   // Consuming iterator
v.sort();                        // Sort in place
v.dedup();                       // Remove consecutive duplicates
v.retain(|x| *x > 2);           // Keep matching elements
&v[0..2];                        // Slice

HashMap

rust
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert("key", 42);
map.get("key");                  // -> Option<&V>
map.contains_key("key");        // -> bool
map.remove("key");               // -> Option<V>
map.entry("key").or_insert(0);   // Insert if absent
*map.entry("key").or_insert(0) += 1; // Increment

for (key, val) in &map {
    println!("{key}: {val}");
}

Option & Result

rust
// Option<T> - value that may or may not exist
enum Option<T> {
    Some(T),
    None,
}

let x: Option<i32> = Some(42);
x.unwrap();                      // 42 (panics if None)
x.unwrap_or(0);                  // 42 or default
x.unwrap_or_default();           // Uses T::default()
x.map(|v| v * 2);               // Some(84)
x.and_then(|v| Some(v + 1));    // Chained computation
x.is_some();                     // true
x.is_none();                     // false
if let Some(val) = x { }        // Pattern match

// Result<T, E> - operation that can fail
enum Result<T, E> {
    Ok(T),
    Err(E),
}

let r: Result<i32, String> = Ok(42);
r.unwrap();                      // 42 (panics if Err)
r.expect("should work");        // 42 with custom panic msg
r.map(|v| v * 2);               // Ok(84)
r.map_err(|e| format!("{e}"));  // Transform error
r.is_ok();                       // true
r.is_err();                      // false

// The ? operator (early return on error)
fn read_file(path: &str) -> Result<String, io::Error> {
    let content = fs::read_to_string(path)?;  // Returns Err if fails
    Ok(content.to_uppercase())
}

Pattern Matching

match

rust
let x = 5;
match x {
    1 => println!("one"),
    2 | 3 => println!("two or three"),
    4..=9 => println!("four to nine"),
    _ => println!("other"),
}

// Match with destructuring
match point {
    (0, 0) => println!("origin"),
    (x, 0) => println!("on x-axis at {x}"),
    (0, y) => println!("on y-axis at {y}"),
    (x, y) => println!("at ({x}, {y})"),
}

// Match on enums
enum Command {
    Quit,
    Echo(String),
    Move { x: i32, y: i32 },
}

match cmd {
    Command::Quit => quit(),
    Command::Echo(msg) => println!("{msg}"),
    Command::Move { x, y } => move_to(x, y),
}

// Match guards
match num {
    n if n < 0 => println!("negative"),
    n if n > 0 => println!("positive"),
    _ => println!("zero"),
}

if let & while let

rust
// if let (single pattern)
if let Some(val) = optional {
    println!("got {val}");
}

// let-else (Rust 1.65+)
let Some(val) = optional else {
    return Err("missing value");
};

// while let
while let Some(item) = stack.pop() {
    process(item);
}

Iterators

rust
let nums = vec![1, 2, 3, 4, 5];

// Common iterator methods
nums.iter().map(|x| x * 2);              // Transform
nums.iter().filter(|x| **x > 2);         // Filter
nums.iter().fold(0, |acc, x| acc + x);   // Reduce
nums.iter().sum::<i32>();                 // Sum
nums.iter().product::<i32>();             // Product
nums.iter().count();                      // Count
nums.iter().any(|x| *x > 3);             // Any match?
nums.iter().all(|x| *x > 0);             // All match?
nums.iter().find(|x| **x > 3);           // First match
nums.iter().position(|x| *x > 3);        // Index of first match
nums.iter().enumerate();                  // (index, value) pairs
nums.iter().zip(other.iter());            // Pair up two iterators
nums.iter().take(3);                      // First 3 elements
nums.iter().skip(2);                      // Skip first 2
nums.iter().chain(other.iter());          // Concatenate iterators
nums.iter().flat_map(|x| vec![x, x]);    // Map + flatten
nums.iter().collect::<Vec<_>>();          // Collect into type
nums.iter().cloned().collect::<Vec<_>>(); // Clone + collect

// Chaining
let result: Vec<i32> = nums.iter()
    .filter(|x| **x % 2 == 0)
    .map(|x| x * 10)
    .collect();

Traits & Generics

Traits

rust
// Define a trait
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation
    fn preview(&self) -> String {
        format!("{}...", &self.summarize()[..20])
    }
}

// Implement trait for type
struct Article { title: String, body: String }

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.body)
    }
}

// Trait as parameter
fn notify(item: &impl Summary) {
    println!("Breaking: {}", item.summarize());
}

// Trait bound syntax (equivalent)
fn notify<T: Summary>(item: &T) { }

// Multiple bounds
fn process<T: Summary + Display>(item: &T) { }

// where clause (cleaner for complex bounds)
fn process<T, U>(t: &T, u: &U) -> String
where
    T: Summary + Clone,
    U: Display + Debug,
{
    format!("{} - {}", t.summarize(), u)
}

Common Derive Traits

rust
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

// serde (with serde crate)
#[derive(Serialize, Deserialize)]
struct Config {
    host: String,
    port: u16,
}
TraitPurpose
Debug{:?} formatting
CloneExplicit .clone() copy
CopyImplicit bitwise copy (small types)
PartialEq / Eq== comparison
PartialOrd / Ord<, > comparison
HashHashing (for HashMap keys)
Display{} user-facing formatting
DefaultType::default()
From / IntoType conversions

Generics

rust
// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in &list[1..] {
        if item > max { max = item; }
    }
    max
}

// Generic struct
struct Wrapper<T> {
    value: T,
}

impl<T: Display> Wrapper<T> {
    fn show(&self) {
        println!("{}", self.value);
    }
}

// Generic enum (Option and Result are examples)
enum MyResult<T, E> {
    Ok(T),
    Err(E),
}

Error Handling Patterns

rust
// Custom error type
#[derive(Debug)]
enum AppError {
    NotFound(String),
    Database(sqlx::Error),
    Validation(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::NotFound(msg) => write!(f, "not found: {msg}"),
            Self::Database(e) => write!(f, "database: {e}"),
            Self::Validation(msg) => write!(f, "invalid: {msg}"),
        }
    }
}

impl std::error::Error for AppError {}

// From conversions for ? operator
impl From<sqlx::Error> for AppError {
    fn from(e: sqlx::Error) -> Self {
        AppError::Database(e)
    }
}

// Using thiserror crate (less boilerplate)
#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("not found: {0}")]
    NotFound(String),
    #[error("database error")]
    Database(#[from] sqlx::Error),
}

// Using anyhow crate (application code)
fn main() -> anyhow::Result<()> {
    let config = load_config().context("failed to load config")?;
    Ok(())
}

Cargo Commands

Build & Run

CommandDescription
cargo new projectCreate new binary project
cargo new --lib projectCreate new library project
cargo buildBuild debug binary
cargo build --releaseBuild optimized release binary
cargo runBuild and run
cargo run -- argsRun with arguments
cargo checkCheck compilation without building
cargo cleanRemove target directory

Test & Lint

CommandDescription
cargo testRun all tests
cargo test test_nameRun matching tests
cargo test -- --nocaptureShow println output
cargo test -- --test-threads=1Run tests sequentially
cargo benchRun benchmarks
cargo clippyRun linter
cargo clippy --fixAuto-fix lint warnings
cargo fmtFormat code
cargo fmt --checkCheck formatting

Dependencies

CommandDescription
cargo add serdeAdd dependency
cargo add serde --features deriveAdd with features
cargo add tokio -F fullShort flag for features
cargo remove serdeRemove dependency
cargo updateUpdate dependencies
cargo treeShow dependency tree
cargo auditCheck for vulnerabilities
cargo doc --openGenerate and open docs

Structs, Enums & impl

rust
// Struct with methods
struct Rect { width: f64, height: f64 }

impl Rect {
    // Associated function (constructor)
    fn new(w: f64, h: f64) -> Self {
        Self { width: w, height: h }
    }

    // Method (takes &self)
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// Enum with data
enum Shape {
    Circle(f64),                  // Tuple variant
    Rectangle { w: f64, h: f64 },// Struct variant
    Point,                        // Unit variant
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => std::f64::consts::PI * r * r,
            Shape::Rectangle { w, h } => w * h,
            Shape::Point => 0.0,
        }
    }
}

Closures

rust
// Closure types
let add = |a, b| a + b;                  // Inferred types
let add = |a: i32, b: i32| -> i32 { a + b }; // Explicit types

// Capture modes
let name = String::from("Alice");
let greet = || println!("Hi {name}");     // Borrows name (&)
let greet = move || println!("Hi {name}");// Takes ownership

// Closure traits
// FnOnce  - can be called once (takes ownership)
// FnMut   - can be called multiple times (mutable borrow)
// Fn      - can be called multiple times (immutable borrow)

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}
apply(|x| x * 2, 5)  // 10

Smart Pointers

TypePurposeUse Case
Box<T>Heap allocationRecursive types, trait objects
Rc<T>Reference counting (single thread)Shared ownership
Arc<T>Atomic ref counting (multi thread)Shared ownership across threads
RefCell<T>Interior mutability (runtime checks)Mutate through &self
Mutex<T>Mutual exclusionThread-safe mutation
RwLock<T>Reader-writer lockMany readers, one writer
rust
// Box - heap allocation
let b = Box::new(5);

// Rc - shared ownership
use std::rc::Rc;
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a);           // Increments count, not deep copy

// Arc + Mutex - thread-safe shared state
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let c = Arc::clone(&counter);
*c.lock().unwrap() += 1;

When to Use X vs Y

DecisionChoice AChoice BUse A WhenUse B When
StringString&strOwned, need to modifyBorrowed, read-only
Error libthiserroranyhowLibrary (typed errors)Application (any error)
Smart ptrBox<T>Rc<T>Single ownerMultiple owners (same thread)
Smart ptrRc<T>Arc<T>Single threadMultiple threads
CollectionVec<T>[T; N]Dynamic sizeFixed size, stack allocated
Trait usageimpl Traitdyn TraitStatic dispatch (perf)Dynamic dispatch (flexibility)
CopyCloneCopyExplicit, heap typesImplicit, small stack types
Iteration.iter().into_iter()Borrow elementsConsume collection

Test Yourself
  1. What happens when you assign a String to another variable? Ownership is moved -- the original variable is no longer valid.

  2. Can you have both a mutable and an immutable borrow at the same time? No. You can have multiple &T OR one &mut T, never both.

  3. What operator propagates errors by returning Err early? The ? operator.

  4. What is the difference between unwrap() and expect()? Both panic on None/Err, but expect() lets you provide a custom panic message.

  5. How do you create a vector with initial values?let v = vec![1, 2, 3];

  6. What trait do you derive to enable {:?} debug formatting?Debug

  7. What command runs the Rust linter?cargo clippy

  8. How do you add a dependency with a specific feature enabled?cargo add tokio -F full or cargo add tokio --features full

  9. What is the difference between Box<T> and Rc<T>?Box<T> has a single owner; Rc<T> enables multiple owners via reference counting (single thread).

  10. What pattern do you use to match a specific enum variant and extract its data?if let Some(val) = optional { ... } or match with destructuring.

Common Gotchas

  • Using unwrap() in production code. It panics on None/Err, crashing your program. Use ?, unwrap_or, unwrap_or_default, or proper match/if let.
  • Fighting the borrow checker with clones. Excessive .clone() defeats Rust's zero-cost abstraction goal. Restructure your code to satisfy borrowing rules instead.
  • Forgetting Send + Sync bounds for async. Types shared across async tasks must implement Send. Rc<T> is not Send -- use Arc<T> instead.
  • String vs &str confusion. Use &str for function parameters (accepts both), String when you need ownership. Converting back and forth with .to_string() everywhere is a smell.

One-Liner Summary

Rust guarantees memory safety without garbage collection through ownership and borrowing -- once you internalize the borrow checker, you get C-level performance with zero undefined behavior.

"What I cannot create, I do not understand." — Richard Feynman