Rust Cheatsheet

Control Flow

Use this Rust reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

if / else if / else

if is an expression — it returns a value.

let number = 7;

if number < 5 {
    println!("less than 5");
} else if number == 5 {
    println!("equal to 5");
} else {
    println!("greater than 5");
}

// As expression (arms must return same type)
let msg = if number % 2 == 0 { "even" } else { "odd" };

// In let binding
let abs = if number >= 0 { number } else { -number };

loop

Infinite loop; use break to exit, continue to skip.

loop {
    println!("again!");
    break;   // exit
}

// Return a value from loop
let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2;  // break with value
    }
};
// result == 20

while

let mut n = 0;
while n < 5 {
    println!("{}", n);
    n += 1;
}

// while let: loop while pattern matches
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
    println!("{}", top);
}

for Loops

// Range
for i in 0..5 { print!("{} ", i); }   // 0 1 2 3 4
for i in 0..=5 { print!("{} ", i); }  // 0 1 2 3 4 5
for i in (0..5).rev() { print!("{} ", i); } // 4 3 2 1 0

// Iterator over collection (borrows)
let v = vec![10, 20, 30];
for x in &v { println!("{}", x); }        // &i32
for x in &mut v { *x += 1; }             // &mut i32
for x in v { println!("{}", x); }         // i32, v consumed

// Enumerate
for (i, x) in v.iter().enumerate() {
    println!("[{}] = {}", i, x);
}

// Zip
let keys = ["a", "b", "c"];
let vals = [1, 2, 3];
for (k, v) in keys.iter().zip(vals.iter()) {
    println!("{}: {}", k, v);
}

// Step
for i in (0..20).step_by(5) { print!("{} ", i); } // 0 5 10 15

Loop Labels

Labels allow breaking/continuing an outer loop from an inner one.

'outer: for i in 0..5 {
    'inner: for j in 0..5 {
        if i == 2 && j == 2 {
            break 'outer;      // break the outer loop
        }
        if j == 3 {
            continue 'outer;   // continue the outer loop
        }
        println!("({}, {})", i, j);
    }
}

match

Exhaustive pattern matching — must cover all cases. Each arm is pattern => expression.

let x = 5;
match x {
    1 => println!("one"),
    2 | 3 => println!("two or three"),    // OR pattern
    4..=6 => println!("four to six"),     // range pattern
    n @ 7..=9 => println!("binding: {}", n), // bind to variable
    _ => println!("something else"),       // wildcard/catch-all
}

// Return a value from match
let description = match x {
    1 => "one",
    2..=9 => "single digit",
    _ => "large",
};

// Match on multiple values (tuple)
let pair = (true, 42);
match pair {
    (true, y) if y > 0 => println!("true and positive: {}", y),
    (true, _) => println!("true but not positive"),
    (false, 0) => println!("false and zero"),
    _ => println!("other"),
}

Match Guards

Extra condition after the pattern, using if:

let num = Some(4);
match num {
    Some(x) if x < 5 => println!("less than 5: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

Destructuring in Patterns

// Struct
struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: 7 };
let Point { x, y } = p;          // shorthand
let Point { x: a, y: b } = p;   // rename to a, b

// Tuple struct
struct Color(i32, i32, i32);
let Color(r, g, b) = Color(255, 0, 0);

// Tuple
let (a, b, c) = (1, 2, 3);
let (head, ..) = (1, 2, 3, 4);       // ignore rest
let (.., tail) = (1, 2, 3, 4);

// Enum (see Enums page)
// Nested
let ((a, b), c) = ((1, 2), 3);

// Slice
let [first, second, rest @ ..] = [1, 2, 3, 4][..] else { return; };

if let

Concise match for a single pattern:

let some_value: Option<i32> = Some(7);

if let Some(x) = some_value {
    println!("Got {}", x);
} else {
    println!("Nothing");
}

// With enums
if let Ok(n) = "42".parse::<i32>() {
    println!("Parsed: {}", n);
}

let-else

Run a block if the pattern does NOT match (block must diverge: return, break, continue, or panic!):

fn get_name(opt: Option<String>) -> String {
    let Some(name) = opt else {
        return String::from("unknown");
    };
    name
}

let Ok(n) = "42".parse::<i32>() else {
    panic!("failed to parse");
};

matches! Macro

let x = 5;
let is_small = matches!(x, 1..=9);         // true
let is_one_or_two = matches!(x, 1 | 2);   // false
let opt = Some(42);
let has_value = matches!(opt, Some(n) if n > 0); // true

Iterators and Functional Control Flow

Iterators are lazy — they do nothing until consumed.

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

// Creating iterators
v.iter()          // yields &T
v.iter_mut()      // yields &mut T
v.into_iter()     // yields T (consumes v)

// Adapters (lazy)
.map(|x| x * 2)
.filter(|x| x % 2 == 0)
.filter_map(|x| if x > 0 { Some(x * 2) } else { None })
.flat_map(|x| vec![x, x + 1])
.flatten()
.take(3)              // first 3 items
.take_while(|x| x < &4)
.skip(2)
.skip_while(|x| x < &3)
.enumerate()          // (index, value)
.zip(other.iter())    // combine two iterators
.chain(other.iter())  // concatenate iterators
.peekable()           // peek at next without consuming
.rev()                // reverse (requires ExactSizeIterator)
.cloned()             // &T → T for Copy/Clone types
.copied()             // &T → T for Copy types
.inspect(|x| dbg!(x)) // side-effect for debugging
.step_by(2)
.cycle()              // infinite repetition
.distinct_by(|x| x)  // (external crate)

// Consumers (eager — trigger evaluation)
.collect::<Vec<_>>()
.collect::<HashMap<K, V>>()
.collect::<String>()
.sum::<i32>()
.product::<i32>()
.count()
.min()   .max()
.min_by_key(|x| x.abs())
.max_by(|a, b| a.cmp(b))
.find(|&&x| x > 2)        // Option<&T>
.find_map(|x| if x > 2 { Some(x * 10) } else { None })
.position(|&x| x == 3)    // Option<usize>
.any(|&x| x > 3)          // bool
.all(|&x| x > 0)          // bool
.fold(0, |acc, x| acc + x)  // reduce with accumulator
.reduce(|acc, x| acc + x)   // fold without initial (Option)
.for_each(|x| println!("{}", x))
.last()                    // Option<T>
.nth(2)                    // Option<T>
.partition(|&&x| x % 2 == 0)  // (Vec<T>, Vec<T>)
.unzip()                   // (Vec<A>, Vec<B>) from Vec<(A,B)>
// Example pipeline
let result: Vec<i32> = (1..=10)
    .filter(|x| x % 2 == 0)
    .map(|x| x * x)
    .take(3)
    .collect();
// [4, 16, 36]

Diverging Expressions (!)

// `!` type — never returns
fn forever() -> ! {
    loop {}
}

fn error_out(msg: &str) -> ! {
    panic!("{}", msg);
}

// panic!, process::exit, loop {}, etc. all have type `!`
// This lets them appear in any branch without type mismatch:
let x: i32 = match some_opt {
    Some(n) => n,
    None => panic!("expected value"),   // ! coerces to i32
};