Rust Essentials

Essential Rust patterns covering ownership, borrowing, error handling, iterators, and common idioms. Master these to write idiomatic Rust code.

Use Case

Use these patterns when you need to:

  • Understand Rust's ownership system
  • Handle errors properly
  • Work with iterators and collections
  • Write safe, concurrent code

Ownership & Borrowing

Move Semantics

 1// Ownership transfer (move)
 2let s1 = String::from("hello");
 3let s2 = s1;  // s1 is moved, no longer valid
 4// println!("{}", s1);  // Error: value borrowed after move
 5
 6// Clone for deep copy
 7let s3 = s2.clone();
 8println!("{} {}", s2, s3);  // Both valid
 9
10// Copy trait for stack types
11let x = 5;
12let y = x;  // Copy, both valid
13println!("{} {}", x, y);

Borrowing

 1// Immutable borrow
 2fn calculate_length(s: &String) -> usize {
 3    s.len()  // Can read but not modify
 4}
 5
 6let s = String::from("hello");
 7let len = calculate_length(&s);
 8println!("Length of '{}' is {}", s, len);  // s still valid
 9
10// Mutable borrow
11fn append_world(s: &mut String) {
12    s.push_str(", world!");
13}
14
15let mut s = String::from("hello");
16append_world(&mut s);
17println!("{}", s);  // "hello, world!"
18
19// Rules:
20// - One mutable reference OR multiple immutable references
21// - References must always be valid

Error Handling

Result Type

 1use std::fs::File;
 2use std::io::{self, Read};
 3
 4// Return Result
 5fn read_file(path: &str) -> Result<String, io::Error> {
 6    let mut file = File::open(path)?;  // ? operator propagates errors
 7    let mut contents = String::new();
 8    file.read_to_string(&mut contents)?;
 9    Ok(contents)
10}
11
12// Pattern matching
13match read_file("data.txt") {
14    Ok(contents) => println!("File: {}", contents),
15    Err(e) => eprintln!("Error: {}", e),
16}
17
18// unwrap_or_else for default
19let contents = read_file("data.txt")
20    .unwrap_or_else(|_| String::from("default content"));

Custom Errors

 1use std::fmt;
 2
 3#[derive(Debug)]
 4enum MyError {
 5    NotFound(String),
 6    InvalidInput(String),
 7    IoError(std::io::Error),
 8}
 9
10impl fmt::Display for MyError {
11    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12        match self {
13            MyError::NotFound(msg) => write!(f, "Not found: {}", msg),
14            MyError::InvalidInput(msg) => write!(f, "Invalid: {}", msg),
15            MyError::IoError(e) => write!(f, "IO error: {}", e),
16        }
17    }
18}
19
20impl std::error::Error for MyError {}
21
22// Using custom error
23fn process_data(input: &str) -> Result<String, MyError> {
24    if input.is_empty() {
25        return Err(MyError::InvalidInput("empty input".to_string()));
26    }
27    Ok(input.to_uppercase())
28}

Iterators

Common Patterns

 1let numbers = vec![1, 2, 3, 4, 5];
 2
 3// map, filter, collect
 4let doubled: Vec<i32> = numbers.iter()
 5    .map(|x| x * 2)
 6    .collect();
 7
 8let evens: Vec<i32> = numbers.iter()
 9    .filter(|x| *x % 2 == 0)
10    .copied()
11    .collect();
12
13// fold (reduce)
14let sum: i32 = numbers.iter().sum();
15let product: i32 = numbers.iter().product();
16let custom = numbers.iter().fold(0, |acc, x| acc + x);
17
18// find, any, all
19let first_even = numbers.iter().find(|x| *x % 2 == 0);
20let has_even = numbers.iter().any(|x| x % 2 == 0);
21let all_positive = numbers.iter().all(|x| *x > 0);
22
23// chain iterators
24let a = vec![1, 2, 3];
25let b = vec![4, 5, 6];
26let combined: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
27
28// enumerate
29for (i, value) in numbers.iter().enumerate() {
30    println!("{}: {}", i, value);
31}

Pattern Matching

 1enum Message {
 2    Quit,
 3    Move { x: i32, y: i32 },
 4    Write(String),
 5    ChangeColor(i32, i32, i32),
 6}
 7
 8fn process_message(msg: Message) {
 9    match msg {
10        Message::Quit => println!("Quit"),
11        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
12        Message::Write(text) => println!("Text: {}", text),
13        Message::ChangeColor(r, g, b) => println!("Color: ({}, {}, {})", r, g, b),
14    }
15}
16
17// if let for single pattern
18let some_value = Some(3);
19if let Some(x) = some_value {
20    println!("Got: {}", x);
21}
22
23// while let
24let mut stack = vec![1, 2, 3];
25while let Some(top) = stack.pop() {
26    println!("{}", top);
27}

Traits

 1// Define trait
 2trait Summary {
 3    fn summarize(&self) -> String;
 4    
 5    // Default implementation
 6    fn summarize_author(&self) -> String {
 7        String::from("Unknown")
 8    }
 9}
10
11// Implement trait
12struct Article {
13    title: String,
14    content: String,
15}
16
17impl Summary for Article {
18    fn summarize(&self) -> String {
19        format!("{}: {}", self.title, &self.content[..50])
20    }
21}
22
23// Trait bounds
24fn notify<T: Summary>(item: &T) {
25    println!("Breaking news! {}", item.summarize());
26}
27
28// Multiple traits
29fn process<T: Summary + Clone>(item: &T) {
30    // Can use both Summary and Clone methods
31}
32
33// where clause for complex bounds
34fn complex<T, U>(t: &T, u: &U) -> String
35where
36    T: Summary + Clone,
37    U: Summary,
38{
39    format!("{} and {}", t.summarize(), u.summarize())
40}

Lifetimes

 1// Lifetime annotations
 2fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
 3    if x.len() > y.len() {
 4        x
 5    } else {
 6        y
 7    }
 8}
 9
10// Struct with lifetime
11struct ImportantExcerpt<'a> {
12    part: &'a str,
13}
14
15impl<'a> ImportantExcerpt<'a> {
16    fn level(&self) -> i32 {
17        3
18    }
19    
20    fn announce_and_return_part(&self, announcement: &str) -> &str {
21        println!("Attention: {}", announcement);
22        self.part
23    }
24}

Smart Pointers

 1use std::rc::Rc;
 2use std::cell::RefCell;
 3
 4// Box for heap allocation
 5let b = Box::new(5);
 6println!("b = {}", b);
 7
 8// Rc for multiple ownership
 9let a = Rc::new(vec![1, 2, 3]);
10let b = Rc::clone(&a);
11let c = Rc::clone(&a);
12println!("Reference count: {}", Rc::strong_count(&a));  // 3
13
14// RefCell for interior mutability
15let data = RefCell::new(5);
16*data.borrow_mut() += 1;
17println!("{}", data.borrow());  // 6
18
19// Combining Rc and RefCell
20let shared_data = Rc::new(RefCell::new(vec![1, 2, 3]));
21let clone1 = Rc::clone(&shared_data);
22clone1.borrow_mut().push(4);
23println!("{:?}", shared_data.borrow());  // [1, 2, 3, 4]

Concurrency

 1use std::thread;
 2use std::sync::{Arc, Mutex};
 3
 4// Spawn threads
 5let handle = thread::spawn(|| {
 6    println!("Hello from thread!");
 7});
 8handle.join().unwrap();
 9
10// Shared state with Arc and Mutex
11let counter = Arc::new(Mutex::new(0));
12let mut handles = vec![];
13
14for _ in 0..10 {
15    let counter = Arc::clone(&counter);
16    let handle = thread::spawn(move || {
17        let mut num = counter.lock().unwrap();
18        *num += 1;
19    });
20    handles.push(handle);
21}
22
23for handle in handles {
24    handle.join().unwrap();
25}
26
27println!("Result: {}", *counter.lock().unwrap());  // 10

Common Macros

 1// vec! macro
 2let v = vec![1, 2, 3];
 3
 4// println! and format!
 5println!("Hello, {}!", "world");
 6let s = format!("x = {}, y = {}", 10, 20);
 7
 8// assert! and debug_assert!
 9assert!(2 + 2 == 4);
10debug_assert!(expensive_check());  // Only in debug builds
11
12// matches! macro
13let foo = 'f';
14assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
15
16// dbg! macro
17let a = 2;
18let b = dbg!(a * 2) + 1;  // Prints: [src/main.rs:2] a * 2 = 4

Notes

  • Ownership prevents data races at compile time
  • Use & for borrowing, &mut for mutable borrowing
  • ? operator simplifies error propagation
  • Iterators are zero-cost abstractions
  • Traits enable polymorphism without inheritance

Gotchas/Warnings

  • ⚠️ Borrowing rules: Can't have mutable and immutable borrows simultaneously
  • ⚠️ Lifetimes: Compiler infers most, explicit only when ambiguous
  • ⚠️ Clone vs Copy: Clone is explicit, Copy is implicit for simple types
  • ⚠️ unwrap(): Panics on error - use in prototypes only
comments powered by Disqus