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,&mutfor 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