Rust Cheatsheet
Enums and Pattern Matching
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 Enums
// Simple enum (C-style) enum Direction { North, South, East, West, } // Enum with associated data — each variant can hold different types enum Message { Quit, // no data Move { x: i32, y: i32 }, // named fields (struct-like) Write(String), // single value ChangeColor(i32, i32, i32), // tuple-like } // Enum with explicit discriminants enum Status { Active = 1, Inactive = 2, Pending = 3, }
Instantiating Enum Variants
let dir = Direction::North; let msg1 = Message::Quit; let msg2 = Message::Move { x: 10, y: 20 }; let msg3 = Message::Write(String::from("hello")); let msg4 = Message::ChangeColor(255, 0, 128); let code = Status::Active as i32; // 1 — cast C-style enum to int
Methods on Enums
impl Message { fn call(&self) { match self { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to ({}, {})", x, y), Message::Write(s) => println!("Write: {}", s), Message::ChangeColor(r, g, b) => println!("Color: {}, {}, {}", r, g, b), } } fn is_quit(&self) -> bool { matches!(self, Message::Quit) } }
match on Enums
let msg = Message::Move { x: 5, y: 10 }; match msg { Message::Quit => { println!("Quit"); } Message::Move { x, y } => { println!("Move to ({}, {})", x, y); } Message::Write(text) => { println!("Write: {}", text); } Message::ChangeColor(r, g, b) => { println!("Color: {} {} {}", r, g, b); } } // Binding with @ let n = 15u32; match n { 0 => println!("zero"), small @ 1..=9 => println!("small: {}", small), large @ 10..=99 => println!("large: {}", large), _ => println!("huge"), } // Ignoring fields match msg { Message::Move { x, .. } => println!("x = {}", x), _ => {} }
Option\<T\>
The standard "nullable" type — no null pointers in Rust.
enum Option<T> { Some(T), None, }
let some_val: Option<i32> = Some(42); let no_val: Option<i32> = None; // Unpacking methods some_val.unwrap() // 42, panics if None some_val.expect("should have value") some_val.unwrap_or(0) // 42 or 0 some_val.unwrap_or_else(|| 0) some_val.unwrap_or_default() // Default::default() // Query some_val.is_some() // true some_val.is_none() // false // Transform some_val.map(|x| x * 2) // Some(84) some_val.map_or(0, |x| x * 2) // 84 (or 0 for None) some_val.and_then(|x| Some(x + 1)) // Some(43) — flatMap some_val.or(Some(99)) // self if Some, other if None some_val.or_else(|| Some(99)) some_val.filter(|&x| x > 50) // None (42 < 50) some_val.flatten() // Option<Option<T>> → Option<T> some_val.zip(Some("hi")) // Some((42, "hi")) // Mutation let mut opt = Some(42); opt.take() // returns Some(42), opt is now None opt.replace(99) // returns None (old value), opt is Some(99) opt.get_or_insert(0) // returns &mut 99 // Convert some_val.ok_or("error") // Result<i32, &str> some_val.ok_or_else(|| "error".to_string()) some_val.as_ref() // Option<&i32> some_val.as_mut() // Option<&mut i32> // Pattern matching if let Some(x) = some_val { println!("{}", x); } let Some(x) = some_val else { return; }; // let-else // ? in functions returning Option fn find_double(v: &[i32], target: i32) -> Option<i32> { let idx = v.iter().position(|&x| x == target)?; // return None if not found Some(v[idx] * 2) }
Result\<T, E\>
enum Result<T, E> { Ok(T), Err(E), }
let ok: Result<i32, String> = Ok(42); let err: Result<i32, String> = Err("something went wrong".to_string()); // Unwrapping ok.unwrap() // 42, panics if Err ok.expect("should be ok") ok.unwrap_or(0) ok.unwrap_or_else(|e| { eprintln!("{}", e); 0 }) ok.unwrap_or_default() // Query ok.is_ok() // true ok.is_err() // false // Transform ok.map(|x| x * 2) // Ok(84) ok.map_err(|e| format!("Error: {}", e)) // transform error ok.and_then(|x| Ok(x + 1)) // Ok(43) — flatMap on success ok.or(Err("other")) // self if Ok, other if Err ok.or_else(|e| Err(format!("!{}", e))) // Extract ok.ok() // Option<T> — discards error ok.err() // Option<E> — discards success // Iteration ok.iter() // iterates 0 or 1 elements for val in &ok { println!("{}", val); }
The ? Operator (Error Propagation)
use std::num::ParseIntError; fn parse_and_double(s: &str) -> Result<i32, ParseIntError> { let n: i32 = s.parse()?; // return Err if parse fails Ok(n * 2) } // Equivalent to: fn parse_and_double_explicit(s: &str) -> Result<i32, ParseIntError> { let n: i32 = match s.parse() { Ok(v) => v, Err(e) => return Err(e), }; Ok(n * 2) } // Works in functions returning Option too: fn first_double(v: &[i32]) -> Option<i32> { let first = v.first()?; // return None if empty Some(first * 2) } // ? converts error type using From trait use std::io; fn read_file(path: &str) -> Result<String, io::Error> { let content = std::fs::read_to_string(path)?; // io::Error auto-converted Ok(content) }
Comprehensive Pattern Matching
Binding Patterns
let x = 5; match x { n @ 1..=10 => println!("n = {}", n), // bind matched value n => println!("other: {}", n), }
Guard Conditions
let pair = (2, -2); match pair { (x, y) if x == y => println!("equal"), (x, y) if x + y == 0 => println!("zero sum"), // matches (2, -2) _ => println!("other"), }
OR Patterns
let x = 'e'; match x { 'a' | 'e' | 'i' | 'o' | 'u' => println!("vowel"), 'a'..='z' => println!("consonant"), _ => println!("other"), }
Nested and Destructuring Patterns
struct Point { x: i32, y: i32 } enum Shape { Circle(Point, f64), Rectangle(Point, Point) } let shape = Shape::Circle(Point { x: 0, y: 0 }, 5.0); match shape { Shape::Circle(Point { x, y }, r) => { println!("Circle at ({},{}) r={}", x, y, r); } Shape::Rectangle(Point { x: x1, y: y1 }, Point { x: x2, y: y2 }) => { println!("Rect ({},{}) to ({},{})", x1, y1, x2, y2); } } // Ignore fields with .. let Point { x, .. } = Point { x: 3, y: 5 }; // Ignore specific fields with _ match (1, 2, 3) { (first, _, third) => println!("{} {}", first, third), } // Ignore entire value with _name (no unused warning) let _unused = 42;
Matching References
let reference = &4; match reference { &val => println!("Got a value via destructuring: {}", val), } // Match guard automatically references let v = vec![1, 2, 3]; match v.iter().find(|&&x| x > 1) { Some(&n) => println!("Found: {}", n), None => println!("Not found"), }
Slice Patterns
let v = vec![1, 2, 3, 4, 5]; match v.as_slice() { [] => println!("empty"), [x] => println!("one: {}", x), [x, y] => println!("two: {} {}", x, y), [first, .., last] => println!("first={} last={}", first, last), [head, tail @ ..] => println!("head={} tail_len={}", head, tail.len()), }
Enum with Generic Data
#[derive(Debug)] enum Tree<T> { Leaf(T), Node { value: T, left: Box<Tree<T>>, right: Box<Tree<T>> }, } let tree = Tree::Node { value: 1, left: Box::new(Tree::Leaf(2)), right: Box::new(Tree::Leaf(3)), };
Deriving Traits for Enums
#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Color { Red, Green, Blue } use std::collections::HashMap; let mut counts: HashMap<Color, i32> = HashMap::new(); counts.insert(Color::Red, 3); println!("{:?}", Color::Green); // Green let c = Color::Red.clone(); assert_eq!(c, Color::Red);
Non-Exhaustive Enums
// In a library: mark enum as non-exhaustive to allow adding variants later // without breaking downstream code #[non_exhaustive] pub enum Error { IoError, ParseError, } // Downstream users MUST include a _ wildcard arm: match err { Error::IoError => {}, Error::ParseError => {}, _ => {}, // required even though all known variants are covered }