Quick reference

Cheat sheets πŸ“‹

Everything you keep forgetting, on one page. Print it (Ctrl/⌘+P; we styled it for paper) or keep it in a tab.

⚑ Syntax essentials

Variables

let x = 5;            // immutable
let mut y = 5;        // mutable
y += 1;               // ok, y is mut
const MAX: u32 = 100; // constant
let x = x * 2;        // shadowing

Functions

fn add(a: i32, b: i32) -> i32 {
    a + b   // last expression = return
}

fn greet(name: &str) {  // no return
    println!("Hi, {name}!");
}

Control flow

if x > 5 { ... } else { ... }

let label = if x > 5 { "big" } else { "small" };

for i in 0..10 { ... }      // 0 to 9
for item in &my_vec { ... } // borrow items
while x < 100 { ... }
loop { break; }             // forever, until break

Printing

println!("plain text");
println!("value: {x}");        // inline var
println!("sum: {}", a + b);    // expression
println!("debug: {:?}", vec);  // Debug view
println!("pretty: {:#?}", s);  // multi-line
eprintln!("to stderr");

πŸ”’ Common types

TypeWhat it isExample
i32whole numbers (default)let n: i32 = -7;
u32 / usizeonly-positive numbers / indexeslet len: usize = v.len();
f64decimals (default float)let pi = 3.14;
booltrue / falselet on = true;
charone character (any emoji too)let c = 'πŸ¦€';
&strborrowed text viewlet s = "hi";
Stringowned, growable textlet s = String::from("hi");
(i32, bool)tuple: mixed bundlelet t = (5, true);
[i32; 3]array: fixed sizelet a = [1, 2, 3];
Vec<T>growable listlet v = vec![1, 2, 3];
Option<T>maybe a valueSome(5) / None
Result<T, E>success or failureOk(5) / Err(e)

🧠 Ownership & borrowing: the whole law

The three ownership rules

  • Every value has exactly one owner.
  • When the owner goes out of scope, the value is dropped (freed).
  • Assigning or passing a heap value moves ownership (unless you .clone()).
let s1 = String::from("hi");
let s2 = s1;        // s1 MOVED into s2
// println!("{s1}"); // ❌ error: s1 is gone
let s3 = s2.clone(); // deep copy, both valid

The two borrowing rules

  • Any number of shared borrows &T, or exactly one mutable borrow &mut T.
  • No reference may outlive the data it points to.
fn shout(msg: &String) { ... }      // reads
fn extend(msg: &mut String) { ... } // edits

let mut s = String::from("hi");
shout(&s);        // lend it (read-only)
extend(&mut s);   // lend it (writable)
// s is still ours afterwards βœ…

🧩 Pattern matching

match

match number {
    0 => println!("zero"),
    1 | 2 => println!("one or two"),
    3..=9 => println!("3 through 9"),
    n if n < 0 => println!("negative"),
    _ => println!("something else"),
}

Option & Result patterns

match find_user(id) {
    Some(user) => greet(&user),
    None => println!("no such user"),
}

if let Some(user) = find_user(id) {
    greet(&user);   // only-one-case shortcut
}

let n: i32 = "42".parse()?;  // ? bubbles Err up
let n = "42".parse().unwrap_or(0);

πŸ“š Collections in practice

Vec: a growable list

let mut v = vec![1, 2, 3];
v.push(4);                 // add to end
let last = v.pop();        // Option<i32>
let first = v.first();     // Option<&i32>
let n = v[0];              // panics if empty!
let n = v.get(0);          // safe: Option
for x in &v { ... }        // iterate borrowed
v.len();  v.is_empty();  v.contains(&2);

String: owned text

let mut s = String::from("Hello");
s.push_str(", world");     // append &str
s.push('!');               // append char
let joined = format!("{s} x{n}");
s.to_uppercase();  s.trim();  s.len();
for word in s.split(' ') { ... }
let num: i32 = "42".parse().unwrap();

HashMap: key β†’ value

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert("ferris", 100);
let s = scores.get("ferris"); // Option<&i32>
*scores.entry("crab").or_insert(0) += 1;
for (name, score) in &scores { ... }

Iterator one-liners

let doubled: Vec<i32> =
    v.iter().map(|x| x * 2).collect();

let evens: Vec<i32> =
    v.iter().filter(|x| *x % 2 == 0)
            .cloned().collect();

let total: i32 = v.iter().sum();
let biggest = v.iter().max();   // Option
v.iter().count();  v.iter().rev();
v.iter().enumerate();  // (index, item)

🚨 Error handling

The toolbox

result.unwrap();      // crash on Err 😬
result.expect("msg"); // crash with message
result.unwrap_or(0);  // fallback value
result.is_ok();       // just checking
result?               // pass Err to caller
option.ok_or("gone")? // Option β†’ Result

Propagating with ?

use std::fs;

fn read_config() -> Result<String, std::io::Error> {
    let text = fs::read_to_string("cfg.txt")?;
    Ok(text.trim().to_string())
}
// caller decides what a missing file means

πŸ“¦ Cargo commands

CommandWhat it does
cargo new my_appcreate a new project
cargo runcompile + run in one step
cargo checkfast "does it compile?" (no binary)
cargo build --releaseoptimized build in target/release
cargo testrun all tests
cargo add serdeadd a dependency from crates.io
cargo fmtauto-format all code
cargo clippythe wise linter (catches bad patterns)
cargo doc --openbuild & open docs for your code + deps
rustup updateupdate Rust itself

🧯 Borrow-checker survival guide

When the compiler complains…

  • "value moved here" β†’ pass a reference &value instead, or .clone() if you truly need a copy.
  • "cannot borrow as mutable more than once" β†’ shrink the scope: finish one borrow (often with { } blocks) before starting the next.
  • "cannot borrow as mutable because also borrowed as immutable" β†’ stop reading before you start writing; reorder the code.
  • "does not live long enough" β†’ you're returning a reference to something that dies; return an owned String/Vec instead.

Beginner rules of thumb

  • Functions should usually take &str instead of String, and &[T] instead of Vec<T>.
  • Reach for .clone() guilt-free while learning: correct first, optimal later.
  • Let the compiler drive: read the error, apply its suggestion, repeat.
  • When stuck, cargo clippy often knows the idiomatic fix.
πŸ–¨οΈ Make it paper Press Ctrl/⌘ + P. This page has a clean print layout on purpose. Pin it above your desk like the periodic table of crab.