Rust Interview Questions with Answers
Most Asked Rust Interview Questions for Software Engineer Roles
Introduction
Rust is a modern systems programming language that has taken the tech world by storm. With its unique ownership model, it guarantees memory safety and thread safety without the need for a garbage collector – a feat that few languages can match. Rust is widely used in systems programming, embedded systems, game engines, and WebAssembly development, making it one of the most sought‑after skills in the industry. This comprehensive guide brings you 100+ carefully curated Rust interview questions and answers, covering everything from beginner fundamentals to advanced concepts. You'll master variables, data types, functions, ownership, borrowing, lifetimes, structs, enums, traits, error handling (Result/Option), concurrency (threads and channels), asynchronous programming with Tokio, smart pointers (Box, Rc, Arc, RefCell, Weak), unsafe code, FFI with C, serialization with Serde, and real‑world Rust development scenarios. Whether you're aiming for a systems programming role, an embedded engineer position, or a backend job using Rust, this question bank will help you solidify your understanding and give you the confidence to tackle even the toughest technical challenges. Start practicing now and join the growing community of Rustaceans who are shaping the future of software development.
Why Rust?
- Memory safety without garbage collection – prevents null pointer dereferences and data races
- Zero-cost abstractions – high-level features without performance overhead
- Fearless concurrency – thread safety guaranteed at compile time
- Growing ecosystem and strong community – used at Mozilla, Dropbox, Cloudflare, and more
- Increasingly popular for system programming, embedded, and WebAssembly
- Consistently ranked as one of the most loved programming languages
Most Asked Rust Interview Questions
Rust is a systems programming language focused on safety, speed, and concurrency. It guarantees memory safety without a garbage collector.
- Memory safe: Ownership system prevents memory bugs
- Zero-cost abstractions: High-level features without runtime overhead
- Concurrent: Fearless concurrency
- No garbage collector: Manual memory management with safety
- Systems language: Can write OS kernels, embedded systems, web servers
// Hello World in Rust
fn main() {
println!("Hello, World!");
}Variables in Rust are immutable by default. Use let for immutable and let mut for mutable variables.
- Immutable:
let x = 10 - Mutable:
let mut y = 20 - Type inference:
let z = 42 - Explicit types:
let a: i32 = 10 - Constants:
const PI: f64 = 3.14159 - Shadowing:
let x = 5; let x = x + 1
// Variables in Rust
fn main() {
// Immutable variable (default)
let immutable_var = "Hello";
// Mutable variable
let mut mutable_var = "World";
mutable_var = "Rust";
// Type inference
let inferred = 42;
// Explicit type
let explicit: i32 = 10;
// Constants
const PI: f64 = 3.14159;
// Shadowing
let x = 5;
let x = x + 1;
// Display
println!("{}", immutable_var);
println!("{}", mutable_var);
println!("{}", inferred);
println!("{}", explicit);
println!("{}", PI);
println!("{}", x);
}Rust provides scalar and compound data types including integers, floats, booleans, characters, arrays, tuples, and more.
- Integer: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
- Floating point: f32, f64
- Boolean: bool
- Character: char
- String: String, &str
- Array: [T; N] (fixed size)
- Tuple: (T1, T2, T3)
- Vector: Vec<T> (dynamic size)
// Data Types in Rust
fn main() {
// Integer types
let signed_8: i8 = -128;
let unsigned_8: u8 = 255;
let signed_32: i32 = -1000;
let unsigned_64: u64 = 1000;
let default_int = 42; // i32 by default
// Floating point types
let float_32: f32 = 3.14;
let float_64: f64 = 3.14159;
let default_float = 3.14; // f64 by default
// Boolean
let is_rust_awesome: bool = true;
let is_false = false;
// Character (Unicode)
let character: char = 'A';
let emoji: char = '🚀';
let unicode: char = '中';
// String types
let string_literal: &str = "Hello, world!";
let heap_string: String = String::from("Hello");
let mut mutable_string = String::new();
mutable_string.push_str("Rust");
// Arrays (fixed size)
let array: [i32; 5] = [1, 2, 3, 4, 5];
let first = array[0];
let repeated = [0; 10]; // [0, 0, ..., 0] (10 elements)
// Tuples (fixed size, heterogeneous)
let tuple: (i32, f64, char) = (42, 3.14, 'A');
let (x, y, z) = tuple; // destructuring
let first_element = tuple.0;
// Vectors (dynamic size)
let mut vector: Vec<i32> = vec![1, 2, 3];
vector.push(4);
let second = vector[1];
// Type inference
let inferred_int = 100; // i32
let inferred_float = 3.14; // f64
let inferred_bool = true; // bool
println!("Integers: {}, {}, {}", signed_8, unsigned_32, default_int);
println!("Floats: {}, {}", float_32, float_64);
println!("Boolean: {}", is_rust_awesome);
println!("Character: {}", character);
println!("String: {}", heap_string);
println!("Array: {:?}", array);
println!("Tuple: {:?}", tuple);
println!("Vector: {:?}", vector);
}Functions in Rust are defined with the fn keyword. They can have parameters and return values.
- Basic:
fn add(a: i32, b: i32) -> i32 { a + b } - No return:
fn print() { println!("Hello") } - Multiple returns:
fn divide(a: i32, b: i32) -> (i32, i32) - Generic functions:
fn swap<T>(a: T, b: T) -> (T, T) - Closures:
|a, b| a + b
// Functions in Rust
fn main() {
// Call functions
let sum = add(5, 3);
println!("Sum: {}", sum);
let result = divide(10, 3);
println!("Quotient: {}, Remainder: {}", result.0, result.1);
let (a, b) = swap(10, 20);
println!("Swapped: {}, {}", a, b);
// Closure example
let add_closure = |a, b| a + b;
println!("Closure sum: {}", add_closure(5, 3));
// Higher-order function
let doubled = apply_twice(3, |x| x * 2);
println!("Doubled twice: {}", doubled);
}
// Basic function with return value
fn add(a: i32, b: i32) -> i32 {
a + b // Implicit return (no semicolon)
}
// Function with no return value (returns unit type ())
fn print_message() {
println!("Hello from Rust!");
}
// Function returning multiple values using tuple
fn divide(a: i32, b: i32) -> (i32, i32) {
let quotient = a / b;
let remainder = a % b;
(quotient, remainder)
}
// Generic function
fn swap<T>(a: T, b: T) -> (T, T) {
(b, a)
}
// Function with explicit return (using return keyword)
fn is_even(num: i32) -> bool {
if num % 2 == 0 {
return true;
}
false
}
// Higher-order function (takes closure as parameter)
fn apply_twice<F>(x: i32, f: F) -> i32
where F: Fn(i32) -> i32 {
f(f(x))
}Ownership is Rust's core memory safety rule. Every value has a single owner, and when the owner goes out of scope, the value is dropped. Ownership can be moved (transferred) or borrowed.
- Each value has exactly one owner
- Move: transferring ownership (e.g., assignment, passing to function)
- Clone: deep copy for heap data
- Drop: automatic cleanup when owner goes out of scope
// Arrays and Vectors in Rust
fn main() {
// Array (fixed size)
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let strings: [&str; 3] = ["Apple", "Banana", "Orange"];
let mixed: [i32; 3] = [1, 2, 3];
// Access and modify (arrays are immutable by default)
println!("{}", numbers[2]); // Access element
// numbers[2] = 10; // Not allowed if not mutable
// Array operations
println!("Length: {}", numbers.len());
// Iteration
for num in numbers.iter() {
println!("{}", num);
}
// Vector (dynamic array)
let mut vec_numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
vec_numbers.push(6);
vec_numbers.pop();
vec_numbers[2] = 10;
// Vector operations
let doubled: Vec<i32> = vec_numbers.iter().map(|x| x * 2).collect();
let filtered: Vec<i32> = vec_numbers.iter().filter(|&&x| x > 2).cloned().collect();
let sum: i32 = vec_numbers.iter().sum();
println!("{:?}", doubled);
println!("{:?}", filtered);
println!("{}", sum);
}Borrowing allows you to reference a value without taking ownership. References are either immutable (&T) or mutable (&mut T). The borrow checker enforces rules to prevent data races.
- Immutable reference:
&T– can have multiple readers - Mutable reference:
&mut T– exclusive access - Rule 1: Either one mutable reference or any number of immutable ones
- Rule 2: References must always be valid (no dangling references)
// Collections in Rust
use std::collections::{HashMap, HashSet, VecDeque};
fn main() {
// Vector (List)
let mut vec: Vec<i32> = vec![1, 2, 3, 4, 5];
vec.push(6);
vec.remove(1);
// HashSet (Set)
let mut set: HashSet<i32> = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(3);
set.insert(3); // Won't add duplicate
// HashMap (Dictionary)
let mut map: HashMap<&str, &str> = HashMap::new();
map.insert("key1", "value1");
map.insert("key2", "value2");
map.remove("key1");
// VecDeque (Double-ended queue)
let mut deque: VecDeque<i32> = VecDeque::new();
deque.push_back(1);
deque.push_front(0);
deque.pop_back();
deque.pop_front();
// Collection operations
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).cloned().collect();
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
let sum: i32 = numbers.iter().sum();
println!("{:?}", evens);
println!("{:?}", doubled);
println!("{}", sum);
}String is a growable, heap-allocated string type. &str is a string slice – a view into a string that is typically immutable and can be stack-allocated or part of a String.
- String: Owned, mutable, heap-allocated
- &str: Borrowed, immutable, fixed-size view
- Conversion:
String::from("hello"),my_string.as_str() - Usage:
&strfor function parameters,Stringfor owned data
// Structs (Data Classes) in Rust
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
city: String,
}
impl Person {
// Constructor
fn new(name: String, age: u32, city: String) -> Self {
Person {
name,
age,
city,
}
}
// Method
fn greet(&self) -> String {
format!("Hello, my name is {}", self.name)
}
// Method with default values
fn with_defaults(name: String, age: u32) -> Self {
Person {
name,
age,
city: String::from("Unknown"),
}
}
}
fn main() {
let person1 = Person::new(String::from("Alice"), 25, String::from("NYC"));
let person2 = person1.clone();
// Update using struct update syntax
let person3 = Person {
age: 26,
..person2
};
println!("Name: {}", person1.name);
println!("Age: {}", person1.age);
println!("City: {}", person1.city);
println!("{}", person1.greet());
println!("{:?}", person3);
}A slice is a reference to a contiguous sequence of elements in a collection (like an array or vector). Slices are immutable by default and can be created using the range syntax &[start..end].
- Array slice:
&[i32] - String slice:
&str(already a slice) - Creation:
&arr[1..4](excludes end) - Benefits: No copying, safe access to parts of data
// Enums (Sealed Classes) in Rust
#[derive(Debug)]
enum Result<T, E> {
Success(T),
Error(E),
Loading,
}
#[derive(Debug)]
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Point,
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
Shape::Rectangle(width, height) => width * height,
Shape::Point => 0.0,
}
}
}
// Enum with associated data
#[derive(Debug)]
enum Payment {
Cash(f64),
CreditCard { number: String, expiry: String },
PayPal { email: String },
}
fn handle_payment(payment: Payment) -> String {
match payment {
Payment::Cash(amount) => format!("Cash amount: $\{:.2}", amount),
Payment::CreditCard { number, expiry } => format!("Card: {}, Expiry: {}", number, expiry),
Payment::PayPal { email } => format!("PayPal: {}", email),
}
}
fn main() {
let result = Result::Success(String::from("Data loaded"));
let shape = Shape::Circle(5.0);
let payment = Payment::CreditCard {
number: String::from("1234-5678-9012-3456"),
expiry: String::from("12/25"),
};
println!("{:?}", result);
println!("Area: {}", shape.area());
println!("{}", handle_payment(payment));
}Methods are defined in an impl block for the struct. They can take &self, &mut self, or self as the first parameter.
- Immutable:
fn method(&self) { } - Mutable:
fn method(&mut self) { } - Ownership:
fn method(self) { }(consumes the struct) - Associated functions:
fn new() -> Self(noself)
// Null Safety in Rust (Option and Result)
fn main() {
// Option type (null safety)
let nullable_string: Option<String> = Some(String::from("Hello"));
let null_string: Option<String> = None;
// Safe access with match
match nullable_string {
Some(s) => println!("String is: {}", s),
None => println!("String is null"),
}
// Unwrap with default
let length = nullable_string.as_ref().map_or(0, |s| s.len());
println!("Length: {}", length);
// Elvis operator equivalent using unwrap_or
let value = null_string.unwrap_or(String::from("default"));
println!("Value: {}", value);
// Optional chaining using and_then
let result = nullable_string
.as_ref()
.and_then(|s| Some(s.len()));
println!("Result: {:?}", result);
// If let for simple cases
if let Some(s) = nullable_string {
println!("If let: {}", s);
}
// Result type for operations that can fail
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
match divide(10, 2) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}Enums define a type that can be one of several variants. Pattern matching (match) destructures enums and handles each variant, ensuring exhaustive handling.
- Enum definition:
enum IpAddr { V4(u8, u8, u8, u8), V6(String) } - Match:
match value { Variant1 => ..., _ => ... } - Exhaustive: Compiler checks all variants are handled
- if let: concise matching for a single pattern
// Control Flow in Rust
fn main() {
// If-else
let age = 25;
let status = if age < 18 { "Minor" } else { "Adult" };
println!("{}", status);
// Match (switch replacement)
let grade = 'A';
let result = match grade {
'A' => "Excellent",
'B' => "Good",
'C' => "Fair",
_ => "Needs Improvement",
};
println!("{}", result);
// Match with ranges
let score = 85;
let grade2 = match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
_ => "F",
};
println!("{}", grade2);
// For loop
for i in 0..5 {
println!("{}", i);
}
// For loop with step
for i in (1..10).step_by(2) {
println!("{}", i);
}
// For loop descending
for i in (0..10).rev() {
println!("{}", i);
}
// While loop
let mut i = 0;
while i < 5 {
println!("{}", i);
i += 1;
}
// Loop (infinite loop with break)
let mut i = 0;
loop {
println!("{}", i);
i += 1;
if i >= 5 {
break;
}
}
}Option represents an optional value – either Some(T) or None. Result represents an operation that can succeed (Ok(T)) or fail (Err(E)). They are used extensively for error handling and nullable values.
- Option:
enum Option<T> { Some(T), None } - Result:
enum Result<T, E> { Ok(T), Err(E) } - Methods:
unwrap(),expect(),unwrap_or(),? operator - Use:
? operatorpropagates errors
// Traits and Inheritance in Rust
// Base trait
trait Animal {
fn name(&self) -> &str;
fn make_sound(&self) -> String;
// Default implementation
fn greet(&self) -> String {
format!("Hello, I'm {}", self.name())
}
}
// Dog struct
struct Dog {
name: String,
breed: String,
}
impl Dog {
fn new(name: &str, breed: &str) -> Self {
Dog {
name: String::from(name),
breed: String::from(breed),
}
}
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
fn make_sound(&self) -> String {
String::from("Woof!")
}
}
// Cat struct
struct Cat {
name: String,
color: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
fn make_sound(&self) -> String {
String::from("Meow!")
}
}
// Trait inheritance
trait Flyable {
fn fly(&self) -> String;
}
trait Swimmable {
fn swim(&self) -> String;
}
struct Duck {
name: String,
}
impl Flyable for Duck {
fn fly(&self) -> String {
String::from("Flying")
}
}
impl Swimmable for Duck {
fn swim(&self) -> String {
String::from("Swimming")
}
}
fn main() {
let dog = Dog::new("Rex", "German Shepherd");
let cat = Cat {
name: String::from("Whiskers"),
color: String::from("Black"),
};
let duck = Duck {
name: String::from("Donald"),
};
println!("{} says: {}", dog.name(), dog.make_sound());
println!("{} says: {}", cat.name(), cat.make_sound());
println!("{}", dog.greet());
println!("{}", duck.fly());
println!("{}", duck.swim());
}Result is used for recoverable errors. You can handle errors with match, unwrap_or_else, or the ? operator to propagate errors to the caller.
- match:
match result { Ok(v) => v, Err(e) => ... } - ?:
let value = do_something()?;(propagates error) - unwrap: panic on error (use sparingly)
- Custom errors: define your own error types
// Properties/Fields in Rust
struct Person {
name: String,
age: u32,
email: String,
}
impl Person {
// Constructor
fn new(name: String, age: u32, email: String) -> Self {
Person { name, age, email }
}
// Getter
fn name(&self) -> &str {
&self.name
}
// Getter with transformation
fn age(&self) -> u32 {
self.age
}
// Setter with validation
fn set_age(&mut self, age: u32) -> Result<(), &'static str> {
if age < 0 {
return Err("Age cannot be negative");
}
self.age = age;
Ok(())
}
// Computed property
fn full_name(&self) -> String {
format!("{} (Age: {})", self.name, self.age)
}
// Lazy initialization pattern
fn expensive_data(&self) -> &str {
// In real code, this would compute once and cache
"Expensive Result"
}
}
// Using lazy_static or once_cell for lazy properties
use std::cell::OnceCell;
struct LazyPerson {
name: String,
expensive_data: OnceCell<String>,
}
impl LazyPerson {
fn new(name: String) -> Self {
LazyPerson {
name,
expensive_data: OnceCell::new(),
}
}
fn get_expensive_data(&self) -> &str {
self.expensive_data.get_or_init(|| {
println!("Computing expensive data...");
String::from("Expensive Result")
})
}
}
fn main() {
let mut person = Person::new(String::from("Alice"), 25, String::from("alice@example.com"));
println!("Name: {}", person.name());
println!("Age: {}", person.age());
println!("Full name: {}", person.full_name());
match person.set_age(26) {
Ok(()) => println!("Age updated to: {}", person.age()),
Err(e) => println!("Error: {}", e),
}
let lazy_person = LazyPerson::new(String::from("Bob"));
println!("{}", lazy_person.get_expensive_data());
println!("{}", lazy_person.get_expensive_data()); // Cached
}Traits define shared behavior (similar to interfaces). Generics allow writing code that works with multiple types. Traits can be used as bounds on generic parameters.
- Trait definition:
trait Summary { fn summarize(&self) -> String; } - Implement:
impl Summary for NewsArticle { ... } - Generic function:
fn print<T: Display>(item: T) { ... } - Derived traits:
#[derive(Debug, Clone)]
// Associated Functions and Constants in Rust
struct MyClass {
value: i32,
}
impl MyClass {
// Associated constant
const TAG: &'static str = "MyClass";
// Associated function (similar to static method)
fn new(value: i32) -> Self {
MyClass { value }
}
// Factory method
fn create() -> Self {
MyClass { value: 0 }
}
// Instance method
fn get_value(&self) -> i32 {
self.value
}
// Method that modifies self
fn set_value(&mut self, value: i32) {
self.value = value;
}
}
// Singleton pattern using lazy_static
use std::sync::OnceLock;
struct Config {
api_url: String,
timeout: u32,
}
impl Config {
fn new() -> Self {
Config {
api_url: String::from("https://api.example.com"),
timeout: 5000,
}
}
fn instance() -> &'static Config {
static INSTANCE: OnceLock<Config> = OnceLock::new();
INSTANCE.get_or_init(|| Config::new())
}
}
fn main() {
println!("{}", MyClass::TAG);
let mut obj = MyClass::new(42);
println!("Value: {}", obj.get_value());
obj.set_value(100);
println!("Updated value: {}", obj.get_value());
let config = Config::instance();
println!("API URL: {}", config.api_url);
println!("Timeout: {}", config.timeout);
}Closures are anonymous functions that can capture variables from their environment. They are defined with |params| { body } and implement one of the Fn, FnMut, or FnOnce traits.
- Capture: Can take ownership (
move), borrow mutably, or immutably - Traits:
Fn(immutable borrow),FnMut(mutable borrow),FnOnce(consumes) - Usage:
let add = |x, y| x + y; - Move:
let f = move |x| x + captured;
// Error Handling in Rust
use std::fs::File;
use std::io::{self, Read};
// Custom error type
#[derive(Debug)]
enum MyError {
InvalidAge,
IoError(io::Error),
}
impl From<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::IoError(error)
}
}
// Function that returns Result
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
return Err("Division by zero");
}
Ok(a / b)
}
// Function with custom error
fn validate_age(age: i32) -> Result<(), MyError> {
if age < 0 || age > 150 {
return Err(MyError::InvalidAge);
}
Ok(())
}
// Function with ? operator
fn read_file() -> Result<String, MyError> {
let mut file = File::open("test.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Using match
match divide(10, 2) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Using unwrap_or
let result = divide(10, 0).unwrap_or(0);
println!("Result: {}", result);
// Using ? operator
let result = divide(10, 2)?;
println!("Result: {}", result);
// Using if let
if let Ok(result) = divide(10, 2) {
println!("Result: {}", result);
}
// Custom error handling
match validate_age(200) {
Ok(()) => println!("Age is valid"),
Err(e) => println!("Error: {:?}", e),
}
// Propagating errors
match read_file() {
Ok(contents) => println!("File contents: {}", contents),
Err(e) => println!("Error reading file: {:?}", e),
}
Ok(())
}
// Example with payment enum
enum Payment {
Cash(f64),
CreditCard { number: String, expiry: String },
PayPal { email: String },
}
fn handle_payment(payment: Payment) -> String {
match payment {
Payment::Cash(amount) => format!("Cash amount: $\{:.2}", amount),
Payment::CreditCard { number, expiry } => format!("Card: {}, Expiry: {}", number, expiry),
Payment::PayPal { email } => format!("PayPal: {}", email),
}
}Iterators provide a lazy way to process sequences of values. They implement the Iterator trait and can be combined with adapters like map, filter, fold.
- Creation:
vec.iter(),(0..10).into_iter() - Adapters:
map,filter,take,skip(lazy) - Consumers:
collect(),fold(),sum()(eager) - Lazy: Iterators are lazy – nothing happens until consumed
// Closures (Lambdas) in Rust
fn main() {
// Basic closure
let square = |x: i32| x * x;
// Closure with multiple parameters
let add = |a: i32, b: i32| a + b;
// Closure with multiple lines
let complex = |x: i32| {
let y = x * 2;
y + 10
};
// Higher-order function
fn operate<F>(a: i32, b: i32, operation: F) -> i32
where
F: Fn(i32, i32) -> i32,
{
operation(a, b)
}
// Closure capturing environment
let factor = 2;
let multiply = |x: i32| x * factor;
// Returning closure from function
fn get_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
// Using closures with iterators
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
let filtered: Vec<i32> = numbers.iter().filter(|&&x| x > 2).cloned().collect();
println!("Square: {}", square(5));
println!("Add: {}", add(5, 3));
println!("Complex: {}", complex(5));
println!("Operate: {}", operate(6, 7, |a, b| a * b));
println!("Multiply: {}", multiply(5));
let double = get_multiplier(2);
println!("Double: {}", double(5));
println!("Doubled: {:?}", doubled);
println!("Filtered: {:?}", filtered);
}Vec<T> is a growable, heap-allocated array. Other collections include HashMap, HashSet, BTreeMap, and LinkedList. They are part of the standard library.
- Vector:
let mut v: Vec<i32> = Vec::new(); - Push:
v.push(5); - Access:
v[0]orv.get(0)(returns Option) - Iterate:
for x in &v { ... }
// Scope Functions in Rust
// Rust doesn't have built-in scope functions like Kotlin
// but we can use closures and patterns
struct Person {
name: String,
age: u32,
city: String,
}
fn main() {
// let - execute block (using closure)
let person = Person {
name: String::from("Alice"),
age: 25,
city: String::from("NYC"),
};
// Using a closure for scope
let result = {
let name = &person.name;
let age = person.age;
println!("Name: {}", name);
person.age + 1
};
println!("Result: {}", result);
// apply - configure object (using with)
let updated_person = {
let mut p = Person {
name: person.name.clone(),
age: person.age,
city: person.city.clone(),
};
p.age = 26;
p.city = String::from("SF");
p
};
// also - perform additional operations
let numbers = vec![1, 2, 3];
let processed = {
let mut temp = numbers.clone();
println!("Before: {:?}", temp);
temp.push(4);
println!("After: {:?}", temp);
temp
};
// take-if equivalent using Option
fn take_if<T, F>(value: T, predicate: F) -> Option<T>
where
F: Fn(&T) -> bool,
{
if predicate(&value) {
Some(value)
} else {
None
}
}
let adult = take_if(25, |&age| age >= 18);
println!("Adult: {:?}", adult);
}HashMap<K, V> stores key-value pairs with fast lookups. Keys must implement Eq and Hash. They are useful for counting, caching, and mapping relationships.
- Create:
use std::collections::HashMap; - Insert:
map.insert(key, value); - Get:
map.get(&key)returnsOption<&V> - Entry API:
map.entry(key).or_insert(default)
// Extension Traits in Rust
// Rust uses extension traits to add methods to existing types
// String extensions
trait StringExt {
fn is_email(&self) -> bool;
fn add_prefix(&self, prefix: &str) -> String;
fn word_count(&self) -> usize;
}
impl StringExt for String {
fn is_email(&self) -> bool {
self.contains('@') && self.contains('.')
}
fn add_prefix(&self, prefix: &str) -> String {
format!("{}{}", prefix, self)
}
fn word_count(&self) -> usize {
self.split_whitespace().count()
}
}
impl StringExt for str {
fn is_email(&self) -> bool {
self.contains('@') && self.contains('.')
}
fn add_prefix(&self, prefix: &str) -> String {
format!("{}{}", prefix, self)
}
fn word_count(&self) -> usize {
self.split_whitespace().count()
}
}
// Numeric extensions
trait NumberExt {
fn is_even(&self) -> bool;
fn is_odd(&self) -> bool;
}
impl NumberExt for i32 {
fn is_even(&self) -> bool {
self % 2 == 0
}
fn is_odd(&self) -> bool {
self % 2 != 0
}
}
// List extensions
trait ListExt<T> {
fn second_or_none(&self) -> Option<&T>;
}
impl<T> ListExt<T> for Vec<T> {
fn second_or_none(&self) -> Option<&T> {
if self.len() >= 2 {
Some(&self[1])
} else {
None
}
}
}
fn main() {
let email = String::from("test@example.com");
println!("{}", email.is_email());
let greeting = String::from("Hello").add_prefix("Greeting: ");
println!("{}", greeting);
println!("{}", 5.is_even());
println!("{}", 5.word_count());
let numbers = vec![1, 2, 3];
println!("{:?}", numbers.second_or_none());
}match is a powerful control flow construct that compares a value against a series of patterns and executes code based on which pattern matches. It is exhaustive – all possible values must be covered.
- Syntax:
match value { Pattern1 => expr1, Pattern2 => expr2, _ => default } - Patterns: literals, variables, wildcards, ranges, destructuring
- Exhaustive: compiler checks all cases
- if guard:
Pattern if condition => ...
// Type Aliases in Rust
// Type aliases for complex types
type Operation = Box<dyn Fn(i32, i32) -> i32>;
type UserMap = std::collections::HashMap<String, (String, i32)>;
type UserId = u64;
type UserName = String;
type ResultCallback = Box<dyn Fn(String)>;
// Using type aliases
fn execute<F>(op: F, a: i32, b: i32) -> i32
where
F: Fn(i32, i32) -> i32,
{
op(a, b)
}
// Function type alias
type OperationFn = fn(i32, i32) -> i32;
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
// Tuple type alias
type User = (String, i32);
fn main() {
// Using Operation type
let add_op: Operation = Box::new(|a, b| a + b);
let multiply_op: Operation = Box::new(|a, b| a * b);
println!("{}", execute(*add_op, 5, 3));
println!("{}", execute(*multiply_op, 5, 3));
// Using OperationFn type
let add_fn: OperationFn = add;
let multiply_fn: OperationFn = multiply;
println!("{}", add_fn(5, 3));
println!("{}", multiply_fn(5, 3));
// Using UserMap type
let mut users: UserMap = std::collections::HashMap::new();
users.insert(String::from("user1"), (String::from("Alice"), 25));
users.insert(String::from("user2"), (String::from("Bob"), 30));
if let Some((name, age)) = users.get("user1") {
println!("User1: {} ({})", name, age);
}
// Using User type
let user: User = (String::from("Alice"), 25);
println!("User: {}, {}", user.0, user.1);
}if let is a concise way to match a single pattern. while let loops while a pattern matches. They are syntactic sugar for match when you only care about one variant.
- if let:
if let Some(x) = optional { ... } - while let:
while let Some(x) = iterator.next() { ... } - else:
if let ... else { ... } - Usage: Reduce boilerplate for single-pattern matching
// Inline Functions in Rust
// Rust uses inline attribute for performance optimization
// Inline function
#[inline]
fn square(x: i32) -> i32 {
x * x
}
// Inline always
#[inline(always)]
fn add(a: i32, b: i32) -> i32 {
a + b
}
// Inline never
#[inline(never)]
fn complex_calculation(x: i32) -> i32 {
let y = x * 2;
y + 10
}
// Macros (compile-time code generation)
macro_rules! measure_time {
($block:expr) => {{
use std::time::Instant;
let start = Instant::now();
let result = $block;
let duration = start.elapsed();
println!("Time: {:?}", duration);
result
}};
}
// Generic function with inline
#[inline]
fn process<T, F>(value: T, transform: F) -> T
where
F: Fn(T) -> T,
{
transform(value)
}
fn main() {
// Using inline functions
println!("Square: {}", square(5));
println!("Add: {}", add(5, 3));
println!("Complex: {}", complex_calculation(5));
// Using macro
let result = measure_time!({
std::thread::sleep(std::time::Duration::from_millis(100));
42
});
println!("Result: {}", result);
// Generic inline
let result = process(5, |x| x * 2);
println!("Processed: {}", result);
}const defines compile-time constants. static defines global variables that have a fixed memory location and can be mutable (static mut) but require unsafe access.
- const: compile‑time, inlined, no fixed address
- static: global, has fixed address, can be accessed anywhere
- static mut: mutable global – requires
unsafeto read/write - Lazy static:
lazy_static!for runtime initialization
// Higher-Order Functions in Rust
fn main() {
// Function that takes a function as parameter
fn apply_operation<F>(a: i32, b: i32, operation: F) -> i32
where
F: Fn(i32, i32) -> i32,
{
operation(a, b)
}
// Function that returns a function
fn get_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
// Function composition
fn compose<A, B, C>(f: impl Fn(B) -> C, g: impl Fn(A) -> B) -> impl Fn(A) -> C {
move |x| f(g(x))
}
// Higher-order function with multiple closures
fn process<F, G>(value: i32, transform: F, filter: G) -> Option<i32>
where
F: Fn(i32) -> i32,
G: Fn(i32) -> bool,
{
if filter(value) {
Some(transform(value))
} else {
None
}
}
// Usage with closures
let result = apply_operation(10, 20, |a, b| a + b);
println!("Result: {}", result);
let double = get_multiplier(2);
println!("Double: {}", double(5));
let square = |x: i32| x * x;
let add_ten = |x: i32| x + 10;
let square_then_add_ten = compose(add_ten, square);
println!("Square then add ten: {}", square_then_add_ten(5));
let processed = process(5, |x| x * 2, |&x| x > 3);
println!("Processed: {:?}", processed);
// Using with named functions
fn add(a: i32, b: i32) -> i32 {
a + b
}
println!("Named: {}", apply_operation(10, 20, add));
// Iterator higher-order functions
let numbers = vec![1, 2, 3, 4, 5];
let squared: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
let even: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).cloned().collect();
let sum: i32 = numbers.iter().sum();
println!("Squared: {:?}", squared);
println!("Even: {:?}", even);
println!("Sum: {}", sum);
}Type aliases (type) allow giving a new name to an existing type. They improve readability and reduce repetition, especially for complex types like Result<Vec<u8>, Error>.
- Declaration:
type Kilometers = i32; - Usage:
let x: Kilometers = 5; - Generic:
type MyResult<T> = Result<T, MyError>; - No new type: Only a synonym, not type-safe
// Async/Await in Rust
use tokio::time::{sleep, Duration};
// Basic async function
async fn fetch_data() -> String {
sleep(Duration::from_secs(1)).await;
String::from("Data loaded")
}
// Async function with timeout
async fn fetch_with_timeout() -> Result<String, &'static str> {
tokio::time::timeout(
Duration::from_millis(500),
fetch_data()
).await.map_err(|_| "Timed out")
}
// Multiple async tasks
async fn parallel_tasks() -> Vec<String> {
let task1 = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
String::from("Task 1")
});
let task2 = tokio::spawn(async {
sleep(Duration::from_millis(500)).await;
String::from("Task 2")
});
let results = tokio::try_join!(task1, task2).unwrap();
vec![results.0, results.1]
}
// Async with select!
async fn select_example() -> String {
let data = tokio::spawn(async {
sleep(Duration::from_millis(500)).await;
String::from("Data ready")
});
let timeout = tokio::spawn(async {
sleep(Duration::from_millis(1000)).await;
String::from("Timeout")
});
tokio::select! {
result = data => result.unwrap(),
result = timeout => result.unwrap(),
}
}
// Async stream (using tokio-stream)
use tokio_stream::StreamExt;
async fn stream_example() {
let mut stream = tokio_stream::iter(0..10);
while let Some(value) = stream.next().await {
println!("Stream value: {}", value);
}
}
#[tokio::main]
async fn main() {
let data = fetch_data().await;
println!("{}", data);
match fetch_with_timeout().await {
Ok(data) => println!("Data: {}", data),
Err(e) => println!("Error: {}", e),
}
let results = parallel_tasks().await;
println!("Results: {:?}", results);
let result = select_example().await;
println!("Select result: {}", result);
stream_example().await;
}Newtype is a wrapper around an existing type that creates a new distinct type. It provides type safety and allows adding custom methods or traits to the wrapped type.
- Definition:
struct Age(i32); - Access: use
.0to get inner value - Traits: can derive common traits like
Debug - Benefits: Prevents mixing units (e.g., Age vs Weight)
// Iterators and Streams in Rust
fn main() {
// Basic iterator
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
println!("Doubled: {:?}", doubled);
// Iterator with filter
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).cloned().collect();
println!("Evens: {:?}", evens);
// Iterator with reduce
let sum: i32 = numbers.iter().sum();
println!("Sum: {}", sum);
// Custom iterator using generator-like pattern
struct Counter {
current: u32,
max: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
let counter = Counter { current: 0, max: 10 };
let nums: Vec<u32> = counter.collect();
println!("Counter: {:?}", nums);
// Lazy iterators
let lazy = (0..10)
.map(|x| {
println!("Mapping: {}", x);
x * 2
})
.filter(|x| {
println!("Filtering: {}", x);
x % 3 == 0
});
let result: Vec<i32> = lazy.take(3).collect();
println!("Result: {:?}", result);
// Iterator adapters
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let processed: Vec<i32> = numbers
.iter()
.skip(2)
.take(5)
.map(|&x| x * 2)
.filter(|&x| x % 3 == 0)
.collect();
println!("Processed: {:?}", processed);
}Deref allows customizing the dereference operator (*) and enables smart pointers to behave like references. Drop allows custom code to run when a value goes out of scope (like a destructor).
- Deref:
impl Deref for MyType { type Target = T; fn deref(&self) -> &T { ... } } - Drop:
impl Drop for MyType { fn drop(&mut self) { ... } } - Deref coercion: auto conversion to the target type
- Drop order: dropped in reverse order of declaration
// Channels in Rust (MPSC)
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// Basic channel
let (tx, rx) = mpsc::channel();
// Producer thread
let tx1 = tx.clone();
thread::spawn(move || {
for i in 1..5 {
tx1.send(i).unwrap();
thread::sleep(Duration::from_millis(100));
}
});
// Producer thread 2
let tx2 = tx.clone();
thread::spawn(move || {
for i in 5..10 {
tx2.send(i).unwrap();
thread::sleep(Duration::from_millis(150));
}
});
// Consumer
for received in rx {
println!("Received: {}", received);
}
// Async channel (tokio)
#[tokio::main]
async fn async_channel_example() {
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let tx_clone = tx.clone();
tokio::spawn(async move {
for i in 1..5 {
tx_clone.send(i).await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
}
});
tokio::spawn(async move {
for i in 5..10 {
tx.send(i).await.unwrap();
tokio::time::sleep(Duration::from_millis(150)).await;
}
});
while let Some(value) = rx.recv().await {
println!("Async received: {}", value);
}
}
// Broadcast channel
use tokio::sync::broadcast;
#[tokio::main]
async fn broadcast_example() {
let (tx, mut rx1) = broadcast::channel(16);
let mut rx2 = tx.subscribe();
tokio::spawn(async move {
let _ = tx.send(42);
});
tokio::spawn(async move {
if let Ok(value) = rx1.recv().await {
println!("Receiver 1: {}", value);
}
});
tokio::spawn(async move {
if let Ok(value) = rx2.recv().await {
println!("Receiver 2: {}", value);
}
});
tokio::time::sleep(Duration::from_millis(100)).await;
}
}RefCell provides interior mutability – it allows mutable access even when the value is shared, by enforcing borrowing rules at runtime instead of compile time. It is single-threaded.
- RefCell:
let x = RefCell::new(5); - Borrow:
let y = x.borrow();(immutable) - Borrow mut:
let mut y = x.borrow_mut(); - Panic: if rules are violated at runtime (e.g., double borrow)
// Enums and Pattern Matching in Rust
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}
#[derive(Debug)]
enum Status {
Success(u32),
Error(String),
Loading,
}
// Enum with methods
impl Status {
fn is_success(&self) -> bool {
matches!(self, Status::Success(_))
}
fn get_code(&self) -> Option<u32> {
match self {
Status::Success(code) => Some(*code),
_ => None,
}
}
}
// Discriminated union pattern
enum UiState {
Success { data: String },
Error { message: String },
Loading,
Idle,
}
impl UiState {
fn is_loading(&self) -> bool {
matches!(self, UiState::Loading)
}
fn data(&self) -> Option<&String> {
match self {
UiState::Success { data } => Some(data),
_ => None,
}
}
}
fn main() {
let color = Color::Red;
let status = Status::Success(200);
let state = UiState::Success {
data: String::from("Data loaded"),
};
// Pattern matching
match color {
Color::Red => println!("Color is Red"),
Color::Green => println!("Color is Green"),
Color::Blue => println!("Color is Blue"),
}
match status {
Status::Success(code) => println!("Success with code: {}", code),
Status::Error(msg) => println!("Error: {}", msg),
Status::Loading => println!("Loading..."),
}
// If let
if let UiState::Success { data } = state {
println!("Data: {}", data);
}
// While let
let mut vec = vec![1, 2, 3];
while let Some(value) = vec.pop() {
println!("Popped: {}", value);
}
// Pattern matching with guards
let number = 5;
match number {
n if n < 0 => println!("Negative"),
n if n == 0 => println!("Zero"),
n if n > 0 => println!("Positive"),
_ => println!("Unknown"),
}
}Box<T> allocates memory on the heap and provides ownership. Rc<T> (reference counted) allows multiple owners by keeping a reference count. It is single‑threaded.
- Box:
let b = Box::new(5);– single owner, heap - Rc:
let r = Rc::new(5);– shared ownership - Clone:
let r2 = Rc::clone(&r);increments count - Weak:
Rc::downgrade()to avoid cycles
// Generics in Rust
// Generic struct
struct Box<T> {
value: T,
}
impl<T> Box<T> {
fn new(value: T) -> Self {
Box { value }
}
fn get_value(&self) -> &T {
&self.value
}
}
// Generic function
fn swap<T>(first: T, second: T) -> (T, T) {
(second, first)
}
// Generic with constraints
fn sum_numbers<T: std::ops::Add<Output = T> + Copy + From<u8>>(items: &[T]) -> T {
let mut sum = T::from(0);
for &item in items {
sum = sum + item;
}
sum
}
// Generic with multiple constraints
trait Display {
fn display(&self) -> String;
}
trait Countable {
fn count(&self) -> usize;
}
fn process<T: Display + Countable>(item: &T) {
println!("{}", item.display());
println!("Count: {}", item.count());
}
// Variance - Covariant (using lifetimes)
struct Producer<'a, T> {
value: &'a T,
}
// Contravariant
struct Consumer<T> {
_phantom: std::marker::PhantomData<fn(T)>,
}
fn main() {
// Generic struct
let box_int = Box::new(42);
let box_string = Box::new(String::from("Hello"));
println!("Box int: {}", box_int.get_value());
println!("Box string: {}", box_string.get_value());
// Generic function
let (a, b) = swap(1, 2);
println!("Swapped: {}, {}", a, b);
// Generic with constraints
let numbers = vec![1, 2, 3, 4, 5];
println!("Sum: {}", sum_numbers(&numbers));
// Generic with multiple constraints
struct MyData {
value: String,
}
impl Display for MyData {
fn display(&self) -> String {
format!("Data: {}", self.value)
}
}
impl Countable for MyData {
fn count(&self) -> usize {
self.value.len()
}
}
let data = MyData { value: String::from("Hello") };
process(&data);
}Arc (Atomic Reference Counted) is a thread‑safe version of Rc. It uses atomic operations for reference counting, allowing shared ownership across threads.
- Arc:
let a = Arc::new(5); - Clone:
let a2 = Arc::clone(&a); - Thread-safe: can be sent between threads
- Weak:
Arc::downgrade()to avoid cycles
// Traits and Delegation in Rust
// Base trait
trait Repository {
fn get_data(&self) -> String;
fn save_data(&self, data: &str);
}
// Implementation
struct DatabaseRepository;
impl Repository for DatabaseRepository {
fn get_data(&self) -> String {
String::from("Data from database")
}
fn save_data(&self, data: &str) {
println!("Saving to database: {}", data);
}
}
// Delegation using composition
struct CachedRepository {
delegate: Box<dyn Repository>,
cache: Option<String>,
}
impl CachedRepository {
fn new(delegate: Box<dyn Repository>) -> Self {
CachedRepository {
delegate,
cache: None,
}
}
}
impl Repository for CachedRepository {
fn get_data(&self) -> String {
if let Some(cached) = &self.cache {
return cached.clone();
}
let data = self.delegate.get_data();
// Can't modify self in get_data, so we'd need interior mutability
data
}
fn save_data(&self, data: &str) {
self.delegate.save_data(data);
}
}
// Better delegation with interior mutability
use std::cell::RefCell;
struct CachedRepositoryMutable {
delegate: Box<dyn Repository>,
cache: RefCell<Option<String>>,
}
impl CachedRepositoryMutable {
fn new(delegate: Box<dyn Repository>) -> Self {
CachedRepositoryMutable {
delegate,
cache: RefCell::new(None),
}
}
}
impl Repository for CachedRepositoryMutable {
fn get_data(&self) -> String {
if let Some(cached) = self.cache.borrow().as_ref() {
return cached.clone();
}
let data = self.delegate.get_data();
*self.cache.borrow_mut() = Some(data.clone());
data
}
fn save_data(&self, data: &str) {
self.delegate.save_data(data);
*self.cache.borrow_mut() = None;
}
}
// Lazy property pattern
struct LazyProperty<T> {
value: RefCell<Option<T>>,
init: Box<dyn Fn() -> T>,
}
impl<T> LazyProperty<T> {
fn new<F: Fn() -> T + 'static>(init: F) -> Self {
LazyProperty {
value: RefCell::new(None),
init: Box::new(init),
}
}
fn get(&self) -> T
where
T: Clone,
{
if self.value.borrow().is_none() {
let value = (self.init)();
*self.value.borrow_mut() = Some(value.clone());
value
} else {
self.value.borrow().as_ref().unwrap().clone()
}
}
}
fn main() {
let db = DatabaseRepository;
let cached = CachedRepositoryMutable::new(Box::new(db));
println!("{}", cached.get_data());
println!("{}", cached.get_data()); // Returns cached
cached.save_data("New data");
println!("{}", cached.get_data()); // Cache cleared
// Lazy property
let lazy = LazyProperty::new(|| {
println!("Computing expensive data...");
String::from("Expensive Result")
});
println!("{}", lazy.get());
println!("{}", lazy.get()); // Cached
}Send indicates that ownership of a type can be transferred between threads. Sync indicates that a type can be shared between threads safely (i.e., &T is Send). These are marker traits that the compiler automatically implements for most types.
- Send: allows moving values across threads
- Sync: allows sharing references across threads
- Rc: not Send or Sync
- Arc: both Send and Sync
// Singleton Pattern in Rust
use std::sync::{Arc, Mutex, OnceLock};
// Singleton using OnceLock
struct AppConfig {
api_url: String,
timeout: u32,
}
impl AppConfig {
fn new() -> Self {
AppConfig {
api_url: String::from("https://api.example.com"),
timeout: 5000,
}
}
fn instance() -> &'static AppConfig {
static INSTANCE: OnceLock<AppConfig> = OnceLock::new();
INSTANCE.get_or_init(|| AppConfig::new())
}
fn print_config(&self) {
println!("API URL: {}", self.api_url);
println!("Timeout: {}", self.timeout);
}
}
// Singleton with Mutex for mutable state
struct UserManager {
users: Vec<String>,
}
impl UserManager {
fn new() -> Self {
UserManager {
users: Vec::new(),
}
}
fn instance() -> Arc<Mutex<Self>> {
static INSTANCE: OnceLock<Arc<Mutex<UserManager>>> = OnceLock::new();
INSTANCE
.get_or_init(|| Arc::new(Mutex::new(UserManager::new())))
.clone()
}
fn add_user(&mut self, name: &str) {
self.users.push(name.to_string());
}
fn get_users(&self) -> Vec<String> {
self.users.clone()
}
}
// Singleton using lazy_static
use lazy_static::lazy_static;
lazy_static! {
static ref DATABASE: Mutex<Database> = Mutex::new(Database::new());
}
struct Database {
connected: bool,
}
impl Database {
fn new() -> Self {
Database { connected: true }
}
fn query(&self, sql: &str) -> String {
format!("Executing: {}", sql)
}
}
fn main() {
// Using OnceLock singleton
let config = AppConfig::instance();
config.print_config();
// Using Mutex singleton
let manager = UserManager::instance();
{
let mut mgr = manager.lock().unwrap();
mgr.add_user("Alice");
mgr.add_user("Bob");
}
{
let mgr = manager.lock().unwrap();
println!("Users: {:?}", mgr.get_users());
}
// Using lazy_static singleton
let db = DATABASE.lock().unwrap();
println!("{}", db.query("SELECT * FROM users"));
}async marks a function as asynchronous, returning a Future. await suspends execution until the future completes. Async is built on top of runtimes like Tokio.
- async fn:
async fn fetch() -> String { ... } - await:
let data = fetch().await; - Runtimes: Tokio, async-std
- main:
#[tokio::main]
// Builder Pattern and DSL in Rust
// Builder pattern
#[derive(Debug)]
struct User {
name: String,
age: u32,
email: String,
city: String,
}
struct UserBuilder {
name: Option<String>,
age: Option<u32>,
email: Option<String>,
city: Option<String>,
}
impl UserBuilder {
fn new() -> Self {
UserBuilder {
name: None,
age: None,
email: None,
city: None,
}
}
fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
fn email(mut self, email: &str) -> Self {
self.email = Some(email.to_string());
self
}
fn city(mut self, city: &str) -> Self {
self.city = Some(city.to_string());
self
}
fn build(self) -> Result<User, &'static str> {
Ok(User {
name: self.name.ok_or("Name is required")?,
age: self.age.unwrap_or(0),
email: self.email.unwrap_or_else(|| String::from("No email")),
city: self.city.unwrap_or_else(|| String::from("Unknown")),
})
}
}
// Fluent interface
#[derive(Debug)]
struct Query {
table: String,
select: Vec<String>,
conditions: Vec<String>,
order: Vec<String>,
limit: Option<usize>,
}
impl Query {
fn new(table: &str) -> Self {
Query {
table: table.to_string(),
select: Vec::new(),
conditions: Vec::new(),
order: Vec::new(),
limit: None,
}
}
fn select(mut self, fields: &[&str]) -> Self {
self.select = fields.iter().map(|&s| s.to_string()).collect();
self
}
fn where_condition(mut self, condition: &str) -> Self {
self.conditions.push(condition.to_string());
self
}
fn order_by(mut self, field: &str, desc: bool) -> Self {
let direction = if desc { "DESC" } else { "ASC" };
self.order.push(format!("{} {}", field, direction));
self
}
fn limit(mut self, n: usize) -> Self {
self.limit = Some(n);
self
}
fn build(&self) -> String {
let mut query = String::new();
query.push_str("SELECT ");
if self.select.is_empty() {
query.push_str("*");
} else {
query.push_str(&self.select.join(", "));
}
query.push_str(&format!(" FROM {}", self.table));
if !self.conditions.is_empty() {
query.push_str(&format!(" WHERE {}", self.conditions.join(" AND ")));
}
if !self.order.is_empty() {
query.push_str(&format!(" ORDER BY {}", self.order.join(", ")));
}
if let Some(limit) = self.limit {
query.push_str(&format!(" LIMIT {}", limit));
}
query
}
}
// DSL using macros
macro_rules! html {
($($tag:ident $(($($attr:tt)*))? { $($inner:tt)* })*) => {
format!($(concat!("<", stringify!($tag), ">", html!(@inner $($inner)*), "</", stringify!($tag), ">")),*)
};
(@inner $($inner:tt)*) => {
format!($(stringify!($inner)),*)
};
}
fn main() {
// Builder pattern
let user = UserBuilder::new()
.name("Alice")
.age(25)
.email("alice@example.com")
.city("NYC")
.build()
.unwrap();
println!("User: {:?}", user);
// Query DSL
let query = Query::new("users")
.select(&["name", "age"])
.where_condition("age > 18")
.order_by("name", false)
.limit(10)
.build();
println!("Query: {}", query);
// HTML DSL example
let html = format!("<h1>Hello</h1><p>World</p>");
println!("HTML: {}", html);
}mpsc stands for multiple‑producer, single‑consumer. Channels provide a way to send messages between threads. The standard library provides std::sync::mpsc.
- Sender/Receiver:
let (tx, rx) = mpsc::channel(); - Send:
tx.send(value).unwrap(); - Receive:
let received = rx.recv().unwrap(); - Multiple producers: clone the sender
// Macros and Attributes in Rust
// Basic macro
macro_rules! hello {
() => {
println!("Hello, World!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
($($name:expr),*) => {
$(println!("Hello, {}!", $name);)*
};
}
// Macro with repetition
macro_rules! vec_of_strings {
($($x:expr),*) => {
vec![$($x.to_string()),*]
};
}
// Macro with patterns
macro_rules! calculate {
(add $a:expr, $b:expr) => {
$a + $b
};
(sub $a:expr, $b:expr) => {
$a - $b
};
(mul $a:expr, $b:expr) => {
$a * $b
};
(div $a:expr, $b:expr) => {
$a / $b
};
}
// Attribute macro
#[derive(Debug, Clone, PartialEq)]
struct MyStruct {
name: String,
value: i32,
}
// Custom attribute (derive macro)
#[derive(Default)]
struct MyDefaultStruct {
field1: String,
field2: i32,
}
// Function attribute
#[inline]
fn fast_function(x: i32) -> i32 {
x * 2
}
// Deprecated attribute
#[deprecated(since = "2.0", note = "Use new_function instead")]
fn old_function() {
println!("Old function");
}
// Conditional compilation
#[cfg(target_os = "windows")]
fn platform_specific() {
println!("Running on Windows");
}
#[cfg(target_os = "linux")]
fn platform_specific() {
println!("Running on Linux");
}
fn main() {
// Using macros
hello!();
hello!("Alice");
hello!("Alice", "Bob", "Charlie");
let strings = vec_of_strings!("hello", "world", "rust");
println!("Strings: {:?}", strings);
println!("Add: {}", calculate!(add 5, 3));
println!("Mul: {}", calculate!(mul 5, 3));
// Using attributes
let my_struct = MyStruct {
name: String::from("Test"),
value: 42,
};
println!("{:?}", my_struct);
let default_struct = MyDefaultStruct::default();
println!("Default: {:?}", default_struct);
old_function();
platform_specific();
}Rust provides built‑in macros for testing: assert!, assert_eq!, and assert_ne!. Tests are functions marked with #[test] and run with cargo test.
- Test function:
#[test] fn test_add() { assert_eq!(add(2,2), 4); } - assert: panics if condition false
- Should panic:
#[should_panic] - Integration tests: in
tests/directory
// Reflection and Type Information in Rust
use std::any::{Any, TypeId};
// Trait for type reflection
trait Reflect {
fn type_name(&self) -> &'static str;
fn type_id(&self) -> TypeId;
}
impl<T: 'static> Reflect for T {
fn type_name(&self) -> &'static str {
std::any::type_name::<T>()
}
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
// Struct for reflection examples
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: &str, age: u32) -> Self {
Person {
name: name.to_string(),
age,
}
}
fn greet(&self) -> String {
format!("Hello, my name is {}", self.name)
}
}
// Check type at runtime
fn check_type<T: 'static, U: 'static>() -> bool {
TypeId::of::<T>() == TypeId::of::<U>()
}
// Downcasting
fn downcast_example() {
let value: Box<dyn Any> = Box::new(42);
if let Some(int_val) = value.downcast_ref::<i32>() {
println!("Integer: {}", int_val);
}
let value2: Box<dyn Any> = Box::new(String::from("Hello"));
if let Some(str_val) = value2.downcast_ref::<String>() {
println!("String: {}", str_val);
}
}
// Field reflection (using serde for serialization)
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct SerializedPerson {
name: String,
age: u32,
}
fn reflection_example() {
let person = Person::new("Alice", 25);
// Type name
println!("Type name: {}", person.type_name());
// Type ID
println!("Type ID: {:?}", person.type_id());
// Type checking
println!("Is Person: {}", check_type::<Person, Person>());
println!("Is String: {}", check_type::<Person, String>());
}
fn main() {
reflection_example();
downcast_example();
// Serialization example
let person = SerializedPerson {
name: String::from("Alice"),
age: 25,
};
let serialized = serde_json::to_string(&person).unwrap();
println!("Serialized: {}", serialized);
let deserialized: SerializedPerson = serde_json::from_str(&serialized).unwrap();
println!("Deserialized: {:?}", deserialized);
}Reverse a string using chars().rev().collect() or manual iteration.
- Built-in:
s.chars().rev().collect() - Manual: Iterate from end to start
- Using String:
String::from_str(s).chars().rev().collect() - Complexity: O(n) time
// Reverse a string in Rust
fn reverse_string(s: &str) -> String {
s.chars().rev().collect()
}
fn main() {
let s = "hello";
let reversed = reverse_string(s);
println!("Original: {}", s);
println!("Reversed: {}", reversed);
}Check if a string is a palindrome using iterator methods or two-pointer approach.
- Iterator:
s.chars().eq(s.chars().rev()) - Two-pointer: Compare from both ends
- Case insensitive:
to_lowercase() - Ignoring non-alphanumeric:
filter(|c| c.is_alphanumeric())
// Check palindrome in Rust
fn is_palindrome(s: &str) -> bool {
let cleaned: String = s
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
cleaned == cleaned.chars().rev().collect::<String>()
}
fn is_palindrome_two_pointer(s: &str) -> bool {
let chars: Vec<char> = s
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let mut left = 0;
let mut right = chars.len() - 1;
while left < right {
if chars[left] != chars[right] {
return false;
}
left += 1;
right -= 1;
}
true
}
fn main() {
println!("racecar: {}", is_palindrome("racecar"));
println!("hello: {}", is_palindrome("hello"));
println!("A man a plan a canal Panama: {}", is_palindrome("A man a plan a canal Panama"));
}Find maximum value using iter().max() or manual iteration.
- Built-in:
arr.iter().max() - Manual: Iterate and track max
- Empty array: Returns
None - Complexity: O(n) time
// Find max in array in Rust
fn find_max(arr: &[i32]) -> Option<&i32> {
arr.iter().max()
}
fn find_max_manual(arr: &[i32]) -> Option<i32> {
if arr.is_empty() {
return None;
}
let mut max_val = arr[0];
for &num in arr {
if num > max_val {
max_val = num;
}
}
Some(max_val)
}
fn main() {
let numbers = vec![1, 5, 3, 9, 2];
println!("Max: {:?}", find_max(&numbers));
println!("Max manual: {:?}", find_max_manual(&numbers));
}Remove duplicates using HashSet or manual tracking.
- HashSet:
set.into_iter().collect() - Manual: Track seen elements in vector
- Preserve order: Use
HashSetwith filter - Complexity: O(n) time
// Remove duplicates in Rust
fn remove_duplicates(arr: &[i32]) -> Vec<i32> {
let mut result = Vec::new();
for &item in arr {
if !result.contains(&item) {
result.push(item);
}
}
result
}
fn remove_duplicates_set(arr: &[i32]) -> Vec<i32> {
use std::collections::HashSet;
let set: HashSet<i32> = arr.iter().cloned().collect();
set.into_iter().collect()
}
fn main() {
let numbers = vec![1, 2, 2, 3, 3, 4];
println!("Original: {:?}", numbers);
println!("Unique: {:?}", remove_duplicates(&numbers));
}Merge arrays using extend or concat.
- extend:
arr1.extend(arr2) - concat:
arr1.concat(arr2) - Unique merge: Use
HashSet - Complexity: O(n) time
// Merge arrays in Rust
fn merge_arrays<T: Clone>(arr1: &[T], arr2: &[T]) -> Vec<T> {
let mut result = arr1.to_vec();
result.extend_from_slice(arr2);
result
}
fn merge_unique<T: Clone + Eq + std::hash::Hash>(arr1: &[T], arr2: &[T]) -> Vec<T> {
use std::collections::HashSet;
let set: HashSet<_> = arr1.iter().chain(arr2.iter()).collect();
set.into_iter().cloned().collect()
}
fn main() {
let arr1 = vec![1, 2, 3];
let arr2 = vec![3, 4, 5];
println!("Merged: {:?}", merge_arrays(&arr1, &arr2));
println!("Unique merged: {:?}", merge_unique(&arr1, &arr2));
}Convert string to number using parse().
- parse:
s.parse::<i32>() - Result type: Returns
Result<T, ParseIntError> - Safe conversion: Use
ok()orunwrap_or() - Error handling: Match on
Result
// Convert string to number in Rust
fn string_to_number(s: &str) -> Option<i32> {
s.parse::<i32>().ok()
}
fn string_to_number_safe(s: &str) -> Result<i32, &'static str> {
s.parse::<i32>().map_err(|_| "Invalid number")
}
fn main() {
println!("42: {:?}", string_to_number("42"));
println!("invalid: {:?}", string_to_number("invalid"));
match string_to_number_safe("42") {
Ok(num) => println!("Parsed: {}", num),
Err(e) => println!("Error: {}", e),
}
}Iterate through HashMap using for loop or iter().
- for:
for (key, value) in &map - iter:
map.iter().for_each() - Keys:
map.keys() - Values:
map.values()
// Loop through HashMap in Rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("name", "Alice");
map.insert("age", "25");
map.insert("city", "NYC");
// Using for loop
for (key, value) in &map {
println!("{} => {}", key, value);
}
// Using iter
map.iter().for_each(|(key, value)| {
println!("{} => {}", key, value);
});
// Using keys
for key in map.keys() {
println!("Key: {}", key);
}
// Using values
for value in map.values() {
println!("Value: {}", value);
}
}Delay execution using std::thread::sleep or tokio::time::sleep.
- Thread sleep:
thread::sleep(Duration::from_millis()) - Async sleep:
tokio::time::sleep(Duration::from_millis()) - Blocking: Use
std::thread - Non-blocking: Use
async/await
// Delay function execution in Rust
use std::thread;
use std::time::Duration;
fn delayed_execution(delay_ms: u64, f: impl Fn() + Send + 'static) {
thread::spawn(move || {
thread::sleep(Duration::from_millis(delay_ms));
f();
});
}
// Async delay
use tokio::time::sleep;
async fn async_delay(delay_ms: u64) {
sleep(Duration::from_millis(delay_ms)).await;
}
#[tokio::main]
async fn main() {
delayed_execution(2000, || {
println!("After 2 seconds");
});
// Keep main alive
thread::sleep(Duration::from_millis(2500));
// Async delay
async_delay(1000).await;
println!("After 1 second (async)");
}Make HTTP GET requests using reqwest library.
- reqwest:
reqwest::get(url).await - Synchronous:
reqwest::blocking::get - Async: Use
async/await - JSON parsing:
.json::<T>()
// HTTP GET request in Rust
use reqwest;
#[tokio::main]
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
async fn fetch_json<T: serde::de::DeserializeOwned>(url: &str) -> Result<T, reqwest::Error> {
let response = reqwest::get(url).await?;
let data = response.json::<T>().await?;
Ok(data)
}
#[derive(serde::Deserialize, Debug)]
struct User {
id: u32,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let data = fetch_data("https://jsonplaceholder.typicode.com/users/1").await?;
println!("Data: {}", data);
let user: User = fetch_json("https://jsonplaceholder.typicode.com/users/1").await?;
println!("User: {:?}", user);
Ok(())
}Create a Future using async functions or custom implementations.
- async fn:
async fn fetch() -> String - Custom Future: Implement
Futuretrait - Ready future:
futures::future::ready() - Poll: Override
pollmethod
// Future (Promise-like) in Rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::time::sleep;
// Async function (returns a Future)
async fn fetch_data() -> String {
// Simulate async operation
sleep(Duration::from_millis(100)).await;
String::from("Data fetched!")
}
// Custom Future implementation
struct MyFuture {
completed: bool,
value: Option<String>,
}
impl MyFuture {
fn new() -> Self {
MyFuture {
completed: false,
value: None,
}
}
}
impl Future for MyFuture {
type Output = String;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.completed {
Poll::Ready(self.value.take().unwrap_or_default())
} else {
// Simulate some work
self.completed = true;
self.value = Some(String::from("Custom future result"));
Poll::Ready(self.value.take().unwrap())
}
}
}
// Using ready future from futures crate
use futures::future::ready;
// Combine multiple futures
async fn combine_futures() -> String {
let future1 = fetch_data();
let future2 = fetch_data();
let (result1, result2) = tokio::join!(future1, future2);
format!("{} {}", result1, result2)
}
#[tokio::main]
async fn main() {
// Using async function
let result = fetch_data().await;
println!("Async result: {}", result);
// Using custom future
let custom = MyFuture::new().await;
println!("Custom future: {}", custom);
// Using ready future
let ready_result = ready(String::from("Ready future")).await;
println!("Ready future: {}", ready_result);
// Combining futures
let combined = combine_futures().await;
println!("Combined: {}", combined);
}Calculate factorial using recursion or iteration with product().
- Recursive:
n * factorial(n-1) - Iterative:
(1..=n).product() - Base case:
n <= 1 - Edge cases: 0! = 1
// Factorial in Rust
use std::io;
// Recursive factorial
fn factorial_recursive(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * factorial_recursive(n - 1)
}
}
// Iterative factorial using product()
fn factorial_iterative(n: u64) -> u64 {
(1..=n).product()
}
// Iterative factorial using loop
fn factorial_loop(n: u64) -> u64 {
let mut result = 1;
for i in 1..=n {
result *= i;
}
result
}
// Factorial with error handling for large numbers
fn factorial_safe(n: u64) -> Option<u64> {
let mut result = 1u64;
for i in 1..=n {
result = result.checked_mul(i)?;
}
Some(result)
}
fn main() {
let n = 5;
println!("Factorial of {}:", n);
println!("Recursive: {}", factorial_recursive(n));
println!("Iterative (product): {}", factorial_iterative(n));
println!("Iterative (loop): {}", factorial_loop(n));
// Safe version
match factorial_safe(20) {
Some(result) => println!("Safe factorial: {}", result),
None => println!("Overflow!"),
}
// Input from user
println!("Enter a number: ");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let num: u64 = input.trim().parse().unwrap();
println!("Factorial: {}", factorial_recursive(num));
}Calculate Fibonacci using recursion, iteration, or memoization.
- Recursive:
fib(n-1) + fib(n-2) - Iterative: Loop with variables
- Memoization: Cache results in HashMap
- Complexity: O(n) with memoization
// Fibonacci in Rust
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
n
} else {
fibonacci(n - 1) + fibonacci(n - 2)
}
}
fn fibonacci_iterative(n: u64) -> u64 {
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let temp = a + b;
a = b;
b = temp;
}
a
}
fn main() {
println!("fib(8) = {}", fibonacci(8));
println!("fib(8) iterative = {}", fibonacci_iterative(8));
}FizzBuzz using if-else or match statement.
- Modulo:
i % 15 == 0 - Order: Check 15 first
- Range:
for i in 1..=n - Return vector: Collect results
// FizzBuzz in Rust
fn fizzbuzz(n: u32) -> Vec<String> {
(1..=n)
.map(|i| {
if i % 15 == 0 {
"FizzBuzz".to_string()
} else if i % 3 == 0 {
"Fizz".to_string()
} else if i % 5 == 0 {
"Buzz".to_string()
} else {
i.to_string()
}
})
.collect()
}
fn main() {
let result = fizzbuzz(15);
for (i, item) in result.iter().enumerate() {
println!("{}: {}", i + 1, item);
}
}Find missing number using formula or XOR method.
- Formula:
total - sum - XOR: XOR all numbers and indices
- Complexity: O(n) time
- Edge cases: Empty array, missing first or last
// Find missing number in Rust
fn find_missing(arr: &[i32]) -> i32 {
let n = arr.len() + 1;
let total = n * (n + 1) / 2;
let sum: i32 = arr.iter().sum();
(total - sum) as i32
}
fn find_missing_xor(arr: &[i32]) -> i32 {
let n = arr.len() + 1;
let mut xor_sum = 0;
for i in 1..=n {
xor_sum ^= i as i32;
}
for &num in arr {
xor_sum ^= num;
}
xor_sum
}
fn main() {
let numbers = vec![1, 2, 4, 5, 6];
println!("Missing: {}", find_missing(&numbers));
println!("Missing (XOR): {}", find_missing_xor(&numbers));
}Find duplicates using HashSet or manual tracking.
- HashSet: Track seen elements
- Filter:
arr.iter().filter(|&x| seen.contains(x)) - Complexity: O(n) time
- Returns: Vector of duplicates
// Find duplicates in Rust
use std::collections::HashSet;
fn find_duplicates(arr: &[i32]) -> Vec<i32> {
let mut seen = HashSet::new();
let mut duplicates = HashSet::new();
for &item in arr {
if seen.contains(&item) {
duplicates.insert(item);
} else {
seen.insert(item);
}
}
duplicates.into_iter().collect()
}
fn main() {
let numbers = vec![1, 2, 3, 2, 4, 3];
println!("Duplicates: {:?}", find_duplicates(&numbers));
}Calculate sum using iter().sum() or manual iteration.
- Built-in:
arr.iter().sum() - Manual: Iterate and accumulate
- Fold:
arr.iter().fold(0, |acc, x| acc + x) - Complexity: O(n) time
// Sum of array in Rust
fn sum_array(arr: &[i32]) -> i32 {
arr.iter().sum()
}
fn sum_array_manual(arr: &[i32]) -> i32 {
let mut total = 0;
for &num in arr {
total += num;
}
total
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
println!("Sum: {}", sum_array(&numbers));
println!("Sum manual: {}", sum_array_manual(&numbers));
}Calculate average using sum divided by length.
- Method:
sum / len as f64 - Empty array: Returns
None - Precision: Returns
f64 - Edge case: Handle empty array
// Average of array in Rust
fn average_array(arr: &[f64]) -> Option<f64> {
if arr.is_empty() {
return None;
}
let sum: f64 = arr.iter().sum();
Some(sum / arr.len() as f64)
}
fn main() {
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
println!("Average: {:?}", average_array(&numbers));
}Sort using sort() or sort_unstable().
- sort():
arr.sort() - sort_unstable(): Faster but not stable
- Complexity: O(n log n)
- In-place: Modifies original array
// Sort array ascending in Rust
fn sort_ascending(arr: &[i32]) -> Vec<i32> {
let mut sorted = arr.to_vec();
sorted.sort();
sorted
}
fn main() {
let numbers = vec![5, 2, 8, 1, 9];
println!("Sorted: {:?}", sort_ascending(&numbers));
}Sort descending using sort_by or sort_unstable_by.
- sort_by:
arr.sort_by(|a, b| b.cmp(a)) - Reverse:
arr.sort(); arr.reverse() - Complexity: O(n log n)
- In-place: Modifies original array
// Sort array descending in Rust
fn sort_descending(arr: &[i32]) -> Vec<i32> {
let mut sorted = arr.to_vec();
sorted.sort_by(|a, b| b.cmp(a));
sorted
}
fn main() {
let numbers = vec![5, 2, 8, 1, 9];
println!("Sorted descending: {:?}", sort_descending(&numbers));
}Flatten nested arrays using flat_map or recursion.
- flat_map:
arr.iter().flat_map(|v| v.clone()) - concat:
arr.concat() - Recursive: Check if element is array
- Complexity: O(n) time
// Flatten nested array in Rust
fn flatten_array<T: Clone>(arr: &[Vec<T>]) -> Vec<T> {
arr.iter().flat_map(|v| v.clone()).collect()
}
fn flatten_nested<T: Clone>(arr: &[impl Clone + IntoIterator<Item = T>]) -> Vec<T> {
arr.iter().flat_map(|v| v.clone()).collect()
}
fn main() {
let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
println!("Flattened: {:?}", flatten_array(&nested));
}Split array into chunks using chunks() method.
- chunks:
arr.chunks(size).map(|c| c.to_vec()).collect() - Manual: Iterate with step size
- Edge case: Handle last chunk
- Complexity: O(n) time
// Chunk array in Rust
fn chunk_array<T: Clone>(arr: &[T], size: usize) -> Vec<Vec<T>> {
arr.chunks(size).map(|chunk| chunk.to_vec()).collect()
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
println!("Chunks: {:?}", chunk_array(&numbers, 2));
}Binary search using binary_search() or manual implementation.
- Built-in:
arr.binary_search(&target) - Manual: While loop with left/right pointers
- Requirement: Array must be sorted
- Complexity: O(log n) time
// Binary search in Rust
fn binary_search<T: Ord>(arr: &[T], target: &T) -> Option<usize> {
arr.binary_search(target).ok()
}
fn binary_search_manual<T: Ord>(arr: &[T], target: &T) -> Option<usize> {
let mut left = 0;
let mut right = arr.len() - 1;
while left <= right {
let mid = left + (right - left) / 2;
if &arr[mid] == target {
return Some(mid);
} else if &arr[mid] < target {
left = mid + 1;
} else {
right = mid - 1;
}
}
None
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7];
println!("Index of 5: {:?}", binary_search(&numbers, &5));
println!("Index of 5 manual: {:?}", binary_search_manual(&numbers, &5));
}Quick sort using recursion and partitioning.
- Algorithm: Choose pivot, partition, recurse
- Time: O(n log n) average
- In-place: Implement for performance
- Pivot: First element or random
// Quick sort in Rust
fn quick_sort<T: Ord + Clone>(arr: &[T]) -> Vec<T> {
if arr.len() <= 1 {
return arr.to_vec();
}
let pivot = &arr[0];
let left: Vec<T> = arr.iter().filter(|&x| x < pivot).cloned().collect();
let right: Vec<T> = arr.iter().filter(|&x| x > pivot).cloned().collect();
let mut result = quick_sort(&left);
result.push(pivot.clone());
result.extend(quick_sort(&right));
result
}
fn main() {
let numbers = vec![5, 3, 8, 4, 2, 7, 1, 6];
println!("Sorted: {:?}", quick_sort(&numbers));
}Merge sort using divide-and-conquer and merging.
- Algorithm: Divide, sort, merge
- Time: O(n log n)
- Stable: Maintains relative order
- Space: O(n) auxiliary space
// Merge sort in Rust
fn merge_sort<T: Ord + Clone>(arr: &[T]) -> Vec<T> {
if arr.len() <= 1 {
return arr.to_vec();
}
let mid = arr.len() / 2;
let left = merge_sort(&arr[..mid]);
let right = merge_sort(&arr[mid..]);
merge(&left, &right)
}
fn merge<T: Ord + Clone>(left: &[T], right: &[T]) -> Vec<T> {
let mut result = Vec::with_capacity(left.len() + right.len());
let mut i = 0;
let mut j = 0;
while i < left.len() && j < right.len() {
if left[i] <= right[j] {
result.push(left[i].clone());
i += 1;
} else {
result.push(right[j].clone());
j += 1;
}
}
result.extend_from_slice(&left[i..]);
result.extend_from_slice(&right[j..]);
result
}
fn main() {
let numbers = vec![5, 3, 8, 4, 2, 7, 1, 6];
println!("Sorted: {:?}", merge_sort(&numbers));
}Bubble sort with early termination optimization.
- Algorithm: Compare adjacent, swap
- Time: O(n²) worst case
- Optimization: Stop if no swaps
- In-place: Modifies original array
// Bubble sort in Rust
fn bubble_sort<T: Ord>(arr: &mut [T]) {
let len = arr.len();
for i in 0..len {
let mut swapped = false;
for j in 0..len - i - 1 {
if arr[j] > arr[j + 1] {
arr.swap(j, j + 1);
swapped = true;
}
}
if !swapped {
break;
}
}
}
fn main() {
let mut numbers = vec![5, 3, 8, 4, 2, 7, 1, 6];
bubble_sort(&mut numbers);
println!("Sorted: {:?}", numbers);
}Find common elements using HashSet or iterator methods.
- HashSet:
set1.intersection(&set2).collect() - Filter:
arr1.iter().filter(|x| arr2.contains(x)) - Complexity: O(n) time with HashSet
- Return: Vector of common elements
// Intersection of arrays in Rust
use std::collections::HashSet;
fn intersection<T: Eq + std::hash::Hash + Clone>(arr1: &[T], arr2: &[T]) -> Vec<T> {
let set1: HashSet<_> = arr1.iter().cloned().collect();
let set2: HashSet<_> = arr2.iter().cloned().collect();
set1.intersection(&set2).cloned().collect()
}
fn main() {
let arr1 = vec![1, 2, 3, 4];
let arr2 = vec![3, 4, 5, 6];
println!("Intersection: {:?}", intersection(&arr1, &arr2));
}Combine arrays with unique elements using HashSet.
- HashSet:
set1.union(&set2).collect() - Extend:
set1.extend(set2) - Complexity: O(n) time
- Return: Vector of unique elements
// Union of arrays in Rust
use std::collections::HashSet;
fn union<T: Eq + std::hash::Hash + Clone>(arr1: &[T], arr2: &[T]) -> Vec<T> {
let set1: HashSet<_> = arr1.iter().cloned().collect();
let set2: HashSet<_> = arr2.iter().cloned().collect();
set1.union(&set2).cloned().collect()
}
fn main() {
let arr1 = vec![1, 2, 3];
let arr2 = vec![3, 4, 5];
println!("Union: {:?}", union(&arr1, &arr2));
}Find elements in first array not in second using HashSet.
- HashSet:
set1.difference(&set2).collect() - Symmetric difference:
set1.symmetric_difference(&set2).collect() - Complexity: O(n) time
- Return: Vector of differences
// Difference of arrays in Rust
use std::collections::HashSet;
fn difference<T: Eq + std::hash::Hash + Clone>(arr1: &[T], arr2: &[T]) -> Vec<T> {
let set1: HashSet<_> = arr1.iter().cloned().collect();
let set2: HashSet<_> = arr2.iter().cloned().collect();
set1.difference(&set2).cloned().collect()
}
fn symmetric_difference<T: Eq + std::hash::Hash + Clone>(arr1: &[T], arr2: &[T]) -> Vec<T> {
let set1: HashSet<_> = arr1.iter().cloned().collect();
let set2: HashSet<_> = arr2.iter().cloned().collect();
set1.symmetric_difference(&set2).cloned().collect()
}
fn main() {
let arr1 = vec![1, 2, 3, 4];
let arr2 = vec![3, 4, 5, 6];
println!("Difference: {:?}", difference(&arr1, &arr2));
println!("Symmetric difference: {:?}", symmetric_difference(&arr1, &arr2));
}Group objects by property using HashMap.
- HashMap:
groups.entry(key).or_insert(Vec::new()).push(item) - Iterate: For each item, group by key
- Complexity: O(n) time
- Return: HashMap with grouped items
// Group by property in Rust
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct Item {
type_: String,
name: String,
}
fn group_by_property(items: &[Item], key: &str) -> HashMap<String, Vec<Item>> {
let mut groups = HashMap::new();
for item in items {
let key_value = match key {
"type" => item.type_.clone(),
_ => item.name.clone(),
};
groups.entry(key_value).or_insert_with(Vec::new).push(item.clone());
}
groups
}
fn main() {
let items = vec![
Item { type_: "fruit".to_string(), name: "apple".to_string() },
Item { type_: "fruit".to_string(), name: "banana".to_string() },
Item { type_: "veg".to_string(), name: "carrot".to_string() },
];
let groups = group_by_property(&items, "type");
for (key, items) in &groups {
println!("{}: {:?}", key, items);
}
}Deep clone using Clone trait or manual implementation.
- Clone:
obj.clone()(for Clone types) - Manual: Recursively copy nested structures
- serde: Serialize and deserialize
- Benefits: Complete independent copy
// Deep clone in Rust
#[derive(Debug, Clone)]
struct Address {
city: String,
zip: String,
}
#[derive(Debug, Clone)]
struct User {
name: String,
address: Address,
}
fn deep_clone<T: Clone>(obj: &T) -> T {
obj.clone()
}
fn main() {
let original = User {
name: "Alice".to_string(),
address: Address {
city: "NYC".to_string(),
zip: "10001".to_string(),
},
};
let cloned = deep_clone(&original);
// Since we have a clone, we can modify the clone
// In Rust, clone creates a deep copy by default for structs with Clone
// This would require mutability
// cloned.name = "Bob".to_string();
// cloned.address.city = "LA".to_string();
println!("Original: {:?}", original);
println!("Cloned: {:?}", cloned);
}Perform immutable updates using struct update syntax or serde_json.
- Struct update:
User { age: 26, ..state.user } - Clone: Clone and modify
- serde_json: JSON manipulation
- Return: New immutable object
// Immutable Updates in Rust
use serde_json::json;
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String,
age: u32,
email: String,
}
impl User {
fn new(id: u32, name: &str, age: u32, email: &str) -> Self {
User {
id,
name: name.to_string(),
age,
email: email.to_string(),
}
}
}
fn main() {
let user = User::new(1, "Alice", 25, "alice@example.com");
println!("Original: {:?}", user);
// Method 1: Struct update syntax (immutable)
let updated_user = User {
age: 26,
email: "alice@new.com".to_string(),
..user
};
println!("Updated (struct update): {:?}", updated_user);
// Method 2: Clone and modify
let mut cloned_user = user.clone();
cloned_user.age = 27;
cloned_user.name = "Alice Smith".to_string();
println!("Updated (clone): {:?}", cloned_user);
// Method 3: Functional update using helper function
fn update_user_age(user: &User, new_age: u32) -> User {
User {
age: new_age,
..user.clone()
}
}
let updated_via_fn = update_user_age(&user, 28);
println!("Updated (function): {:?}", updated_via_fn);
// Method 4: Using serde_json for JSON manipulation
let json_user = json!({
"id": user.id,
"name": user.name,
"age": user.age,
"email": user.email
});
println!("JSON user: {}", json_user);
// Modify JSON immutably
let updated_json = json_user.as_object().map(|obj| {
let mut new_obj = obj.clone();
new_obj.insert("age".to_string(), json!(26));
new_obj.insert("email".to_string(), json!("alice@new.com"));
new_obj
});
println!("Updated JSON: {:?}", updated_json);
}Pipe composes functions from left to right using closures.
- Implementation:
|x| f2(f1(x)) - Chaining: Chain multiple functions
- Return: Composed function
- Direction: Left to right
// Pipe function in Rust
fn pipe<T, F1, F2>(f1: F1, f2: F2) -> impl Fn(T) -> T
where
F1: Fn(T) -> T,
F2: Fn(T) -> T,
{
move |x| f2(f1(x))
}
fn compose<T, F1, F2>(f1: F1, f2: F2) -> impl Fn(T) -> T
where
F1: Fn(T) -> T,
F2: Fn(T) -> T,
{
move |x| f1(f2(x))
}
fn main() {
let double = |x: i32| x * 2;
let add_ten = |x: i32| x + 10;
let square = |x: i32| x * x;
let process = pipe(double, add_ten);
let process_composed = compose(add_ten, square);
println!("Pipe: {}", process(5));
println!("Composed: {}", process_composed(5));
}Compose functions from right to left using closures.
- Implementation:
|x| f1(f2(x)) - Chaining: Chain multiple functions
- Return: Composed function
- Direction: Right to left
// Compose function in Rust
fn compose<T, F, G>(f: F, g: G) -> impl Fn(T) -> T
where
F: Fn(T) -> T,
G: Fn(T) -> T,
{
move |x| f(g(x))
}
fn main() {
let double = |x: i32| x * 2;
let add_ten = |x: i32| x + 10;
let square = |x: i32| x * x;
let square_then_add_ten = compose(add_ten, square);
let double_then_square = compose(square, double);
println!("Square then add ten: {}", square_then_add_ten(5));
println!("Double then square: {}", double_then_square(5));
}Cache function results based on arguments using HashMap.
- Cache:
HashMap<T, U> - Key: Function arguments
- Return: Cached or computed result
- Trade-off: Memory for speed
// Memoization in Rust
use std::collections::HashMap;
fn memoize<F, T, U>(mut f: F) -> impl FnMut(T) -> U
where
F: FnMut(T) -> U,
T: Eq + std::hash::Hash + Clone,
U: Clone,
{
let mut cache = HashMap::new();
move |arg| {
if let Some(result) = cache.get(&arg) {
return result.clone();
}
let result = f(arg.clone());
cache.insert(arg, result.clone());
result
}
}
// Fibonacci with memoization
fn fib_memo() -> impl FnMut(u64) -> u64 {
let mut cache = HashMap::new();
move |n| {
if n <= 1 {
return n;
}
if let Some(&result) = cache.get(&n) {
return result;
}
let result = fib_memo()(n - 1) + fib_memo()(n - 2);
cache.insert(n, result);
result
}
}
fn main() {
let mut fib = memoize(|n: u64| {
if n <= 1 {
n
} else {
fib(n - 1) + fib(n - 2)
}
});
println!("Fib(10) = {}", fib(10));
}Ensure a function is called only once using RefCell and flags.
- Closure:
let called = RefCell::new(false) - Result: Cache the result
- Return: Function with guard
- Use case: Initialization
// Once function in Rust
fn once<F, T>(f: F) -> impl Fn() -> T
where
F: Fn() -> T,
T: Clone,
{
use std::cell::RefCell;
let called = RefCell::new(false);
let result = RefCell::new(None);
move || {
if !*called.borrow() {
*called.borrow_mut() = true;
*result.borrow_mut() = Some(f());
}
result.borrow().as_ref().unwrap().clone()
}
}
fn main() {
let initialize = once(|| {
println!("Initialized");
42
});
println!("First call: {}", initialize());
println!("Second call: {}", initialize());
}Debounce with leading edge using timers and timestamp tracking.
- Timer:
thread::sleeportokio::time::sleep - Leading edge: Execute immediately
- Cooldown: Wait before next execution
- Use case: Search input, API calls
// Debounce with leading edge in Rust
use std::time::{Duration, Instant};
use std::thread;
fn debounce_leading<T, F>(delay: Duration, mut f: F) -> impl FnMut() -> T
where
F: FnMut() -> T,
T: Clone,
{
let mut last_call = Instant::now() - delay;
let mut timer = None;
let mut result = None;
move || {
let now = Instant::now();
if now - last_call < delay {
if timer.is_none() {
let last = last_call;
timer = Some(thread::spawn(move || {
thread::sleep(delay - (now - last));
// In a real implementation, we'd need to handle this differently
}));
}
result.clone().unwrap()
} else {
last_call = now;
result = Some(f());
result.clone().unwrap()
}
}
}
fn main() {
let mut debounced = debounce_leading(Duration::from_millis(1000), || {
println!("Executed");
42
});
debounced();
debounced();
}Throttle with leading edge using timestamp tracking.
- Timestamp: Track last execution time
- Leading edge: Execute if enough time passed
- Rate limiting: At most once per period
- Use case: Scroll events, resize
// Throttle with leading edge in Rust
use std::time::{Duration, Instant};
fn throttle_leading<T, F>(delay: Duration, mut f: F) -> impl FnMut() -> T
where
F: FnMut() -> T,
T: Clone,
{
let mut last_call = Instant::now() - delay;
move || {
let now = Instant::now();
if now - last_call >= delay {
last_call = now;
f()
} else {
// Return a default or last value
// In practice, you'd want to return the last result
panic!("Throttled call");
}
}
}
fn main() {
let mut throttled = throttle_leading(Duration::from_millis(1000), || {
println!("Executed");
42
});
throttled();
throttled(); // This will be throttled
}Deep equality comparison using PartialEq trait.
- PartialEq:
#[derive(PartialEq)] - Manual: Recursive comparison
- Arrays: Compare elements recursively
- Objects: Compare fields recursively
// Deep equal in Rust
#[derive(Debug, PartialEq)]
struct Address {
city: String,
zip: String,
}
#[derive(Debug, PartialEq)]
struct User {
name: String,
address: Address,
}
fn deep_equal<T: PartialEq>(a: &T, b: &T) -> bool {
a == b
}
fn main() {
let user1 = User {
name: "Alice".to_string(),
address: Address {
city: "NYC".to_string(),
zip: "10001".to_string(),
},
};
let user2 = User {
name: "Alice".to_string(),
address: Address {
city: "NYC".to_string(),
zip: "10001".to_string(),
},
};
let user3 = User {
name: "Bob".to_string(),
address: Address {
city: "LA".to_string(),
zip: "90001".to_string(),
},
};
println!("User1 == User2: {}", deep_equal(&user1, &user2));
println!("User1 == User3: {}", deep_equal(&user1, &user3));
}Observable pattern with subscribers and notifications using Arc<Mutex>.
- Observable: Maintains subscribers
- Subscribe: Add callback
- Notify: Call all subscribers
- Unsubscribe: Remove callback
// Observable pattern in Rust
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Clone)]
struct Observable<T: Clone + Send + 'static> {
subscribers: Arc<Mutex<Vec<Box<dyn Fn(T) + Send>>>>,
}
impl<T: Clone + Send + 'static> Observable<T> {
fn new() -> Self {
Observable {
subscribers: Arc::new(Mutex::new(Vec::new())),
}
}
fn subscribe<F>(&self, callback: F) -> impl Fn()
where
F: Fn(T) + Send + 'static,
{
let mut subscribers = self.subscribers.lock().unwrap();
subscribers.push(Box::new(callback) as Box<dyn Fn(T) + Send>);
let subscribers = self.subscribers.clone();
move || {
let mut subs = subscribers.lock().unwrap();
// In practice, we'd need to remove the specific callback
// This is a simplified version
}
}
fn notify(&self, data: T) {
let subscribers = self.subscribers.lock().unwrap();
for subscriber in subscribers.iter() {
subscriber(data.clone());
}
}
}
fn main() {
let observable = Observable::new();
let _unsubscribe = observable.subscribe(|data| {
println!("Observer 1 received: {}", data);
});
observable.subscribe(|data| {
println!("Observer 2 received: {}", data);
});
observable.notify("Hello World".to_string());
}Singleton pattern using OnceLock or lazy_static.
- OnceLock:
static INSTANCE: OnceLock<T> = OnceLock::new() - lazy_static:
lazy_static! { static ref INSTANCE: T = T::new() } - Thread-safe: Arc and Mutex for mutable state
- Global access: Through static reference
// Singleton pattern in Rust
use std::sync::{Arc, Mutex, OnceLock};
struct Singleton {
data: String,
}
impl Singleton {
fn new() -> Self {
Singleton {
data: "Singleton data".to_string(),
}
}
fn instance() -> &'static Singleton {
static INSTANCE: OnceLock<Singleton> = OnceLock::new();
INSTANCE.get_or_init(|| Singleton::new())
}
fn get_data(&self) -> &str {
&self.data
}
fn set_data(&mut self, data: String) {
self.data = data;
}
}
struct MutableSingleton {
data: Arc<Mutex<String>>,
}
impl MutableSingleton {
fn new() -> Self {
MutableSingleton {
data: Arc::new(Mutex::new("Mutable singleton data".to_string())),
}
}
fn instance() -> Arc<Mutex<String>> {
static INSTANCE: OnceLock<Arc<Mutex<String>>> = OnceLock::new();
INSTANCE
.get_or_init(|| Arc::new(Mutex::new("Mutable singleton data".to_string())))
.clone()
}
}
fn main() {
let singleton = Singleton::instance();
println!("{}", singleton.get_data());
let data = MutableSingleton::instance();
{
let mut data = data.lock().unwrap();
*data = "Updated data".to_string();
}
let data2 = MutableSingleton::instance();
println!("{}", data2.lock().unwrap());
}Factory pattern using functions or trait objects.
- Factory function:
fn create_user(type: &str) -> Box<dyn User> - Match/if: Determine which type to create
- Return: Box<dyn Trait> for polymorphism
- Benefits: Decouples creation logic
// Factory pattern in Rust
trait User {
fn get_role(&self) -> &'static str;
}
struct Admin;
struct Guest;
struct RegularUser;
impl User for Admin {
fn get_role(&self) -> &'static str {
"admin"
}
}
impl User for Guest {
fn get_role(&self) -> &'static str {
"guest"
}
}
impl User for RegularUser {
fn get_role(&self) -> &'static str {
"regular"
}
}
struct UserFactory;
impl UserFactory {
fn create_user(user_type: &str) -> Box<dyn User> {
match user_type {
"admin" => Box::new(Admin),
"guest" => Box::new(Guest),
_ => Box::new(RegularUser),
}
}
}
fn main() {
let admin = UserFactory::create_user("admin");
let guest = UserFactory::create_user("guest");
println!("Admin role: {}", admin.get_role());
println!("Guest role: {}", guest.get_role());
}Strategy pattern using trait objects or closures.
- Trait:
trait PaymentStrategy - Concrete strategies: Implement trait
- Context: Uses strategy
- Runtime switching: Change strategy at runtime
// Strategy pattern in Rust
trait PaymentStrategy {
fn pay(&self, amount: f64);
}
struct CreditCardStrategy;
struct PayPalStrategy;
struct CryptoStrategy;
impl PaymentStrategy for CreditCardStrategy {
fn pay(&self, amount: f64) {
println!("Paid $\{:.2} with Credit Card", amount);
}
}
impl PaymentStrategy for PayPalStrategy {
fn pay(&self, amount: f64) {
println!("Paid $\{:.2} with PayPal", amount);
}
}
impl PaymentStrategy for CryptoStrategy {
fn pay(&self, amount: f64) {
println!("Paid $\{:.2} with Crypto", amount);
}
}
struct PaymentContext {
strategy: Box<dyn PaymentStrategy>,
}
impl PaymentContext {
fn new(strategy: Box<dyn PaymentStrategy>) -> Self {
PaymentContext { strategy }
}
fn set_strategy(&mut self, strategy: Box<dyn PaymentStrategy>) {
self.strategy = strategy;
}
fn execute_payment(&self, amount: f64) {
self.strategy.pay(amount);
}
}
fn main() {
let mut context = PaymentContext::new(Box::new(CreditCardStrategy));
context.execute_payment(100.0);
context.set_strategy(Box::new(PayPalStrategy));
context.execute_payment(50.0);
}Observer pattern with subject and observers using traits.
- Observer trait:
trait Observer { fn update(&self); } - Subject: Maintains observers
- Attach/Detach: Add/remove observers
- Notify: Call update on all observers
// Observer pattern in Rust
trait Observer {
fn update(&self, data: &str);
}
struct Subject {
observers: Vec<Box<dyn Observer>>,
state: String,
}
impl Subject {
fn new() -> Self {
Subject {
observers: Vec::new(),
state: String::new(),
}
}
fn attach(&mut self, observer: Box<dyn Observer>) {
self.observers.push(observer);
}
fn detach(&mut self, observer: &Box<dyn Observer>) {
// In practice, we'd need to identify the observer
// This is a simplified version
}
fn set_state(&mut self, state: String) {
self.state = state;
self.notify_observers();
}
fn notify_observers(&self) {
for observer in &self.observers {
observer.update(&self.state);
}
}
}
struct ConcreteObserver {
name: String,
}
impl ConcreteObserver {
fn new(name: String) -> Self {
ConcreteObserver { name }
}
}
impl Observer for ConcreteObserver {
fn update(&self, data: &str) {
println!("{} received: {}", self.name, data);
}
}
fn main() {
let mut subject = Subject::new();
let observer1 = ConcreteObserver::new("Observer1".to_string());
let observer2 = ConcreteObserver::new("Observer2".to_string());
subject.attach(Box::new(observer1));
subject.attach(Box::new(observer2));
subject.set_state("Hello World".to_string());
}Decorator pattern using wrapper functions or structs.
- Component: Base object
- Decorator: Wraps component
- Chaining: Multiple decorators
- Benefits: Add behavior dynamically
// Decorator pattern in Rust
#[derive(Clone)]
struct Coffee {
cost: f64,
description: String,
}
impl Coffee {
fn new() -> Self {
Coffee {
cost: 5.0,
description: "Coffee".to_string(),
}
}
}
trait CoffeeDecorator {
fn decorate(&self, coffee: Coffee) -> Coffee;
}
struct MilkDecorator;
struct SugarDecorator;
struct WhippedCreamDecorator;
impl CoffeeDecorator for MilkDecorator {
fn decorate(&self, coffee: Coffee) -> Coffee {
Coffee {
cost: coffee.cost + 2.0,
description: format!("{}, Milk", coffee.description),
}
}
}
impl CoffeeDecorator for SugarDecorator {
fn decorate(&self, coffee: Coffee) -> Coffee {
Coffee {
cost: coffee.cost + 1.0,
description: format!("{}, Sugar", coffee.description),
}
}
}
impl CoffeeDecorator for WhippedCreamDecorator {
fn decorate(&self, coffee: Coffee) -> Coffee {
Coffee {
cost: coffee.cost + 1.5,
description: format!("{}, Whipped Cream", coffee.description),
}
}
}
fn main() {
let coffee = Coffee::new();
let coffee = MilkDecorator.decorate(coffee);
let coffee = SugarDecorator.decorate(coffee);
let coffee = WhippedCreamDecorator.decorate(coffee);
println!("Description: {}", coffee.description);
println!("Cost: $\{:.2}", coffee.cost);
}Command pattern with execute and undo methods using traits.
- Command trait:
trait Command { fn execute(&mut self); fn undo(&mut self); } - Receiver: Performs actual work
- Invoker: Executes commands
- Undo/Redo: Command history
// Command pattern in Rust
trait Command {
fn execute(&mut self);
fn undo(&mut self);
}
struct AddCommand {
receiver: Vec<i32>,
value: i32,
}
impl AddCommand {
fn new(receiver: Vec<i32>, value: i32) -> Self {
AddCommand { receiver, value }
}
}
impl Command for AddCommand {
fn execute(&mut self) {
self.receiver.push(self.value);
}
fn undo(&mut self) {
if let Some(pos) = self.receiver.iter().position(|&x| x == self.value) {
self.receiver.remove(pos);
}
}
}
struct CommandManager {
history: Vec<Box<dyn Command>>,
redo_stack: Vec<Box<dyn Command>>,
}
impl CommandManager {
fn new() -> Self {
CommandManager {
history: Vec::new(),
redo_stack: Vec::new(),
}
}
fn execute(&mut self, mut command: Box<dyn Command>) {
command.execute();
self.history.push(command);
self.redo_stack.clear();
}
fn undo(&mut self) {
if let Some(mut command) = self.history.pop() {
command.undo();
self.redo_stack.push(command);
}
}
fn redo(&mut self) {
if let Some(mut command) = self.redo_stack.pop() {
command.execute();
self.history.push(command);
}
}
}
fn main() {
let receiver = vec![1, 2, 3];
let mut manager = CommandManager::new();
let cmd = AddCommand::new(receiver.clone(), 4);
manager.execute(Box::new(cmd));
manager.undo();
}Memento pattern for state capture and restoration using Clone.
- Originator: Creates and restores mementos
- Memento: Stores state
- Caretaker: Manages mementos
- Undo/Redo: State history
// Memento pattern in Rust
#[derive(Clone)]
struct Memento {
state: String,
}
struct Originator {
state: String,
}
impl Originator {
fn new() -> Self {
Originator {
state: String::new(),
}
}
fn save_state(&self) -> Memento {
Memento {
state: self.state.clone(),
}
}
fn restore_state(&mut self, memento: Memento) {
self.state = memento.state;
}
}
struct Caretaker {
mementos: Vec<Memento>,
}
impl Caretaker {
fn new() -> Self {
Caretaker {
mementos: Vec::new(),
}
}
fn add_memento(&mut self, memento: Memento) {
self.mementos.push(memento);
}
fn get_memento(&self, index: usize) -> Option<&Memento> {
self.mementos.get(index)
}
}
fn main() {
let mut originator = Originator::new();
let mut caretaker = Caretaker::new();
originator.state = "State 1".to_string();
caretaker.add_memento(originator.save_state());
originator.state = "State 2".to_string();
caretaker.add_memento(originator.save_state());
originator.state = "State 3".to_string();
if let Some(memento) = caretaker.get_memento(0) {
originator.restore_state(memento.clone());
println!("Restored state: {}", originator.state);
}
}Mediator pattern for centralized communication using structs.
- Mediator: Encapsulates communication
- Colleague: Communicates through mediator
- Benefits: Loose coupling
- Use case: Chat systems
// Mediator pattern in Rust
struct Mediator {
colleagues: Vec<Colleague>,
}
impl Mediator {
fn new() -> Self {
Mediator {
colleagues: Vec::new(),
}
}
fn register(&mut self, colleague: Colleague) {
self.colleagues.push(colleague);
}
fn send(&self, message: &str, sender: &Colleague) {
for colleague in &self.colleagues {
if colleague.id != sender.id {
colleague.receive(message);
}
}
}
}
struct Colleague {
id: usize,
name: String,
}
impl Colleague {
fn new(id: usize, name: String) -> Self {
Colleague { id, name }
}
fn send(&self, mediator: &Mediator, message: &str) {
mediator.send(message, self);
}
fn receive(&self, message: &str) {
println!("{} received: {}", self.name, message);
}
}
fn main() {
let mut mediator = Mediator::new();
let alice = Colleague::new(1, "Alice".to_string());
let bob = Colleague::new(2, "Bob".to_string());
mediator.register(alice);
mediator.register(bob);
// In practice, we'd need to keep references to the colleagues
// This is a simplified version
}Chain of Responsibility for processing requests sequentially using traits.
- Handler trait:
trait Handler { fn handle(&self, request: &Request) -> Option<String>; } - Chain: Vector of handlers
- Benefits: Decoupling
- Use case: Logging, authentication
// Chain of Responsibility in Rust
trait Handler {
fn handle(&self, request: &Request) -> Option<String>;
}
struct Request {
token: Option<String>,
url: String,
permissions: Vec<String>,
}
struct AuthHandler;
struct LoggerHandler;
struct PermissionHandler;
impl Handler for AuthHandler {
fn handle(&self, request: &Request) -> Option<String> {
if request.token.is_some() {
Some("Authentication passed".to_string())
} else {
Some("Authentication failed".to_string())
}
}
}
impl Handler for LoggerHandler {
fn handle(&self, request: &Request) -> Option<String> {
Some(format!("Logging request: {}", request.url))
}
}
impl Handler for PermissionHandler {
fn handle(&self, request: &Request) -> Option<String> {
if request.permissions.contains(&"read".to_string()) {
Some("Permission granted".to_string())
} else {
Some("Permission denied".to_string())
}
}
}
struct Chain {
handlers: Vec<Box<dyn Handler>>,
}
impl Chain {
fn new() -> Self {
Chain {
handlers: Vec::new(),
}
}
fn add_handler(&mut self, handler: Box<dyn Handler>) {
self.handlers.push(handler);
}
fn handle(&self, request: &Request) -> Vec<String> {
let mut responses = Vec::new();
for handler in &self.handlers {
if let Some(response) = handler.handle(request) {
responses.push(response);
}
}
responses
}
}
fn main() {
let mut chain = Chain::new();
chain.add_handler(Box::new(AuthHandler));
chain.add_handler(Box::new(LoggerHandler));
chain.add_handler(Box::new(PermissionHandler));
let request = Request {
token: Some("valid".to_string()),
url: "/api/data".to_string(),
permissions: vec!["read".to_string()],
};
let responses = chain.handle(&request);
for response in responses {
println!("{}", response);
}
}State pattern for changing behavior with state using trait objects.
- State trait:
trait State { fn handle(&self, context: &mut Context); } - Context: Maintains state
- Transitions: Change between states
- Benefits: Clean state management
// State pattern in Rust
trait State {
fn handle(&self, context: &mut Context);
}
struct ReadyState;
struct ProcessingState;
struct CompletedState;
impl State for ReadyState {
fn handle(&self, context: &mut Context) {
println!("Ready: Waiting for input");
context.state = Box::new(ProcessingState);
}
}
impl State for ProcessingState {
fn handle(&self, context: &mut Context) {
println!("Processing: Working on task");
context.state = Box::new(CompletedState);
}
}
impl State for CompletedState {
fn handle(&self, _context: &mut Context) {
println!("Completed: Task finished");
}
}
struct Context {
state: Box<dyn State>,
}
impl Context {
fn new() -> Self {
Context {
state: Box::new(ReadyState),
}
}
fn request(&mut self) {
self.state.handle(self);
}
}
fn main() {
let mut context = Context::new();
context.request();
context.request();
context.request();
}Proxy pattern for controlling access to objects using traits.
- Subject trait:
trait Subject { fn request(&self); } - Proxy: Controls access
- Lazy loading: Create on demand
- Benefits: Access control, logging
// Proxy pattern in Rust
trait Subject {
fn request(&self);
}
struct RealSubject;
impl Subject for RealSubject {
fn request(&self) {
println!("RealSubject: Handling request");
}
}
struct Proxy {
real_subject: Option<RealSubject>,
}
impl Proxy {
fn new() -> Self {
Proxy {
real_subject: None,
}
}
}
impl Subject for Proxy {
fn request(&self) {
if self.check_access() {
if self.real_subject.is_none() {
// In practice, we'd need interior mutability
// This is a simplified version
}
println!("Proxy: Forwarding request");
}
}
}
impl Proxy {
fn check_access(&self) -> bool {
println!("Proxy: Checking access");
true
}
}
fn main() {
let proxy = Proxy::new();
proxy.request();
}Flyweight pattern for sharing objects to save memory using HashMap.
- Flyweight: Shared object
- Factory: Manages flyweights
- Benefits: Memory optimization
- Use case: Character rendering
// Flyweight pattern in Rust
use std::collections::HashMap;
struct Flyweight {
shared_state: String,
}
impl Flyweight {
fn new(shared_state: String) -> Self {
Flyweight { shared_state }
}
fn operation(&self, unique_state: &str) {
println!("Shared: {}, Unique: {}", self.shared_state, unique_state);
}
}
struct FlyweightFactory {
flyweights: HashMap<String, Flyweight>,
}
impl FlyweightFactory {
fn new() -> Self {
FlyweightFactory {
flyweights: HashMap::new(),
}
}
fn get_flyweight(&mut self, shared_state: String) -> &Flyweight {
self.flyweights
.entry(shared_state.clone())
.or_insert_with(|| {
println!("Creating new flyweight for: {}", shared_state);
Flyweight::new(shared_state)
})
}
}
fn main() {
let mut factory = FlyweightFactory::new();
let fw1 = factory.get_flyweight("state1".to_string());
let fw2 = factory.get_flyweight("state1".to_string());
let fw3 = factory.get_flyweight("state2".to_string());
fw1.operation("unique1");
fw2.operation("unique2");
fw3.operation("unique3");
}Bridge pattern for separating abstraction from implementation using traits.
- Implementation trait:
trait Implementation - Abstraction: High-level interface
- Benefits: Separation of concerns
- Use case: Cross-platform
// Bridge pattern in Rust
trait Implementation {
fn operation_impl(&self);
}
struct ConcreteImplementationA;
struct ConcreteImplementationB;
impl Implementation for ConcreteImplementationA {
fn operation_impl(&self) {
println!("ConcreteImplementationA: Operation");
}
}
impl Implementation for ConcreteImplementationB {
fn operation_impl(&self) {
println!("ConcreteImplementationB: Operation");
}
}
struct Abstraction {
impl_: Box<dyn Implementation>,
}
impl Abstraction {
fn new(impl_: Box<dyn Implementation>) -> Self {
Abstraction { impl_ }
}
fn operation(&self) {
println!("Abstraction: Additional logic");
self.impl_.operation_impl();
}
}
fn main() {
let impl_a = ConcreteImplementationA;
let impl_b = ConcreteImplementationB;
let abstraction1 = Abstraction::new(Box::new(impl_a));
let abstraction2 = Abstraction::new(Box::new(impl_b));
abstraction1.operation();
abstraction2.operation();
}Adapter pattern for converting interfaces using structs.
- Target: Expected interface
- Adaptee: Existing interface
- Adapter: Bridges interfaces
- Benefits: Reusability
// Adapter pattern in Rust
trait Target {
fn request(&self);
}
struct Adaptee;
impl Adaptee {
fn specific_request(&self) {
println!("Adaptee: Specific Request");
}
}
struct Adapter {
adaptee: Adaptee,
}
impl Adapter {
fn new(adaptee: Adaptee) -> Self {
Adapter { adaptee }
}
}
impl Target for Adapter {
fn request(&self) {
self.adaptee.specific_request();
}
}
fn main() {
let adaptee = Adaptee;
let adapter = Adapter::new(adaptee);
adapter.request();
}Facade pattern for simplifying complex subsystems using structs.
- Facade: Simplified interface
- Subsystem: Complex components
- Benefits: Simplified interface
- Use case: Library APIs
// Facade pattern in Rust
struct SubsystemA;
struct SubsystemB;
struct SubsystemC;
impl SubsystemA {
fn operation_a(&self) {
println!("SubsystemA: Operation");
}
}
impl SubsystemB {
fn operation_b(&self) {
println!("SubsystemB: Operation");
}
}
impl SubsystemC {
fn operation_c(&self) {
println!("SubsystemC: Operation");
}
}
struct Facade {
subsystem_a: SubsystemA,
subsystem_b: SubsystemB,
subsystem_c: SubsystemC,
}
impl Facade {
fn new() -> Self {
Facade {
subsystem_a: SubsystemA,
subsystem_b: SubsystemB,
subsystem_c: SubsystemC,
}
}
fn operation(&self) {
println!("Facade: Complex operation");
self.subsystem_a.operation_a();
self.subsystem_b.operation_b();
self.subsystem_c.operation_c();
}
}
fn main() {
let facade = Facade::new();
facade.operation();
}Composite pattern for tree structures using trait objects.
- Component trait:
trait Component { fn operation(&self); } - Leaf: Individual object
- Composite: Container
- Benefits: Uniform interface
// Composite pattern in Rust
trait Component {
fn operation(&self);
}
struct Leaf {
name: String,
}
impl Leaf {
fn new(name: String) -> Self {
Leaf { name }
}
}
impl Component for Leaf {
fn operation(&self) {
println!("Leaf {}: Operation", self.name);
}
}
struct Composite {
name: String,
children: Vec<Box<dyn Component>>,
}
impl Composite {
fn new(name: String) -> Self {
Composite {
name,
children: Vec::new(),
}
}
fn add(&mut self, component: Box<dyn Component>) {
self.children.push(component);
}
fn remove(&mut self, component: Box<dyn Component>) {
// In practice, we'd need to identify the component
// This is a simplified version
}
}
impl Component for Composite {
fn operation(&self) {
println!("Composite {}: Operation", self.name);
for child in &self.children {
child.operation();
}
}
}
fn main() {
let leaf1 = Leaf::new("A".to_string());
let leaf2 = Leaf::new("B".to_string());
let mut composite = Composite::new("Root".to_string());
composite.add(Box::new(leaf1));
composite.add(Box::new(leaf2));
composite.operation();
}Visitor pattern for adding operations without modifying elements using traits.
- Visitor trait:
trait Visitor - Element trait:
trait Element { fn accept(&self, visitor: &dyn Visitor); } - Benefits: Adding operations without modifying
- Use case: Compilers, AST
// Visitor pattern in Rust
trait Visitor {
fn visit_element_a(&self, element: &ElementA);
fn visit_element_b(&self, element: &ElementB);
}
trait Element {
fn accept(&self, visitor: &dyn Visitor);
}
struct ElementA;
struct ElementB;
impl Element for ElementA {
fn accept(&self, visitor: &dyn Visitor) {
visitor.visit_element_a(self);
}
}
impl Element for ElementB {
fn accept(&self, visitor: &dyn Visitor) {
visitor.visit_element_b(self);
}
}
struct ConcreteVisitor;
impl Visitor for ConcreteVisitor {
fn visit_element_a(&self, _element: &ElementA) {
println!("Visiting ElementA");
}
fn visit_element_b(&self, _element: &ElementB) {
println!("Visiting ElementB");
}
}
fn main() {
let visitor = ConcreteVisitor;
let element_a = ElementA;
let element_b = ElementB;
element_a.accept(&visitor);
element_b.accept(&visitor);
}Iterator pattern for sequential access using Iterator trait.
- Iterator trait:
impl Iterator for CustomIterator - Aggregate: Creates iterator
- Benefits: Uniform traversal
- Use case: Collection traversal
// Iterator pattern in Rust
struct CustomIterator<T> {
collection: Vec<T>,
index: usize,
}
impl<T: Clone> CustomIterator<T> {
fn new(collection: Vec<T>) -> Self {
CustomIterator {
collection,
index: 0,
}
}
fn next(&mut self) -> Option<T> {
if self.index < self.collection.len() {
let item = self.collection[self.index].clone();
self.index += 1;
Some(item)
} else {
None
}
}
fn has_next(&self) -> bool {
self.index < self.collection.len()
}
}
fn main() {
let collection = vec![1, 2, 3, 4, 5];
let mut iterator = CustomIterator::new(collection);
while iterator.has_next() {
println!("{}", iterator.next().unwrap());
}
}Template Method for algorithm skeletons using traits.
- Abstract trait:
trait AbstractClass { fn template_method(&self); } - Concrete: Implements steps
- Benefits: Code reuse
- Use case: Frameworks
// Template Method pattern in Rust
trait AbstractClass {
fn template_method(&self) {
self.step1();
self.step2();
self.step3();
}
fn step1(&self) {
println!("Step 1");
}
fn step2(&self);
fn step3(&self) {
println!("Step 3");
}
}
struct ConcreteClass;
impl AbstractClass for ConcreteClass {
fn step2(&self) {
println!("Concrete Step 2");
}
}
fn main() {
let concrete = ConcreteClass;
concrete.template_method();
}Builder pattern for constructing complex objects using structs.
- Builder: Constructs parts
- Director: Orchestrates construction
- Product: Constructed object
- Benefits: Step-by-step construction
// Builder pattern in Rust
struct Product {
parts: Vec<String>,
}
impl Product {
fn new() -> Self {
Product {
parts: Vec::new(),
}
}
fn add(&mut self, part: String) {
self.parts.push(part);
}
fn list_parts(&self) {
println!("{}", self.parts.join(", "));
}
}
struct Builder {
product: Product,
}
impl Builder {
fn new() -> Self {
Builder {
product: Product::new(),
}
}
fn reset(&mut self) {
self.product = Product::new();
}
fn build_step_a(&mut self) {
self.product.add("Part A".to_string());
}
fn build_step_b(&mut self) {
self.product.add("Part B".to_string());
}
fn get_result(&self) -> &Product {
&self.product
}
}
struct Director {
builder: Builder,
}
impl Director {
fn new(builder: Builder) -> Self {
Director { builder }
}
fn build_minimal(&mut self) {
self.builder.build_step_a();
}
fn build_full(&mut self) {
self.builder.build_step_a();
self.builder.build_step_b();
}
}
fn main() {
let mut builder = Builder::new();
let mut director = Director::new(builder);
director.build_minimal();
director.builder.get_result().list_parts();
}Prototype pattern for cloning objects using Clone trait.
- Clone trait:
#[derive(Clone)] - Shallow copy:
clone() - Deep copy: Manual recursive clone
- Benefits: Object reuse, performance
// Prototype pattern in Rust
#[derive(Clone)]
struct Prototype {
name: String,
nested: std::collections::HashMap<String, i32>,
}
impl Prototype {
fn new(name: String, nested: std::collections::HashMap<String, i32>) -> Self {
Prototype { name, nested }
}
fn clone_prototype(&self) -> Self {
self.clone()
}
fn deep_clone(&self) -> Self {
Prototype {
name: self.name.clone(),
nested: self.nested.clone(),
}
}
}
fn main() {
let mut nested = std::collections::HashMap::new();
nested.insert("value".to_string(), 42);
let original = Prototype::new("Original".to_string(), nested);
let copy = original.clone_prototype();
let deep_copy = original.deep_clone();
println!("Original name: {}", original.name);
println!("Copy name: {}", copy.name);
}Error handling using anyhow and thiserror crates for better error management.
- thiserror: Define custom errors
- anyhow: Contextual error handling
- Result:
Result<T, anyhow::Error> - Error chaining:
.context("message")
// Error Handling with anyhow and thiserror
use anyhow::{anyhow, Result};
use thiserror::Error;
#[derive(Error, Debug)]
enum MyError {
#[error("Invalid age: {0}")]
InvalidAge(i32),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
fn validate_age(age: i32) -> Result<(), MyError> {
if age < 0 || age > 150 {
return Err(MyError::InvalidAge(age));
}
Ok(())
}
fn process_data() -> Result<String> {
let result = validate_age(200)
.map_err(|e| anyhow!("Validation failed: {}", e))?;
Ok("Data processed".to_string())
}
fn main() -> Result<()> {
match process_data() {
Ok(data) => println!("{}", data),
Err(e) => println!("Error: {}", e),
}
Ok(())
}Serialization using serde for JSON, YAML, and other formats.
- Serialize:
#[derive(Serialize)] - Deserialize:
#[derive(Deserialize)] - JSON:
serde_json - Custom serialization:
serialize_with
// Serialization with Serde
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Person {
name: String,
age: u32,
email: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct PersonWithOptional {
name: String,
age: u32,
#[serde(default)]
email: Option<String>,
#[serde(default = "default_city")]
city: String,
}
fn default_city() -> String {
"Unknown".to_string()
}
fn main() -> Result<(), serde_json::Error> {
let person = Person {
name: "Alice".to_string(),
age: 25,
email: "alice@example.com".to_string(),
};
let json = serde_json::to_string(&person)?;
println!("Serialized: {}", json);
let deserialized: Person = serde_json::from_str(&json)?;
println!("Deserialized: {:?}", deserialized);
// With optional fields
let person2 = PersonWithOptional {
name: "Bob".to_string(),
age: 30,
email: None,
city: "NYC".to_string(),
};
let json2 = serde_json::to_string(&person2)?;
println!("Serialized with optional: {}", json2);
Ok(())
}Concurrency using std::thread and Arc<Mutex> for shared state.
- Thread spawn:
thread::spawn(|| ) - Shared state:
Arc<Mutex<T>> - Join:
handle.join() - Message passing:
mpsc::channel()
// Concurrency with threads
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Thread with move
let handle = thread::spawn(|| {
for i in 1..5 {
println!("Thread: {}", i);
thread::sleep(std::time::Duration::from_millis(100));
}
});
for i in 1..3 {
println!("Main: {}", i);
thread::sleep(std::time::Duration::from_millis(150));
}
handle.join().unwrap();
// Shared state with Mutex
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}Asynchronous programming using tokio runtime.
- Runtime:
#[tokio::main] - Tasks:
tokio::spawn(async ) - Join:
tokio::try_join! - Timeout:
tokio::time::timeout
// Async with Tokio
use tokio::time::{sleep, Duration};
use tokio::task;
#[tokio::main]
async fn main() {
// Basic async
let handle = task::spawn(async {
sleep(Duration::from_secs(1)).await;
println!("Task completed");
});
handle.await.unwrap();
// Parallel tasks
let task1 = task::spawn(async {
sleep(Duration::from_secs(1)).await;
"Task 1"
});
let task2 = task::spawn(async {
sleep(Duration::from_millis(500)).await;
"Task 2"
});
let results = tokio::try_join!(task1, task2).unwrap();
println!("Results: {:?}", results);
// Timeout
let result = tokio::time::timeout(
Duration::from_millis(500),
async {
sleep(Duration::from_secs(1)).await;
"Success"
}
).await;
match result {
Ok(data) => println!("Success: {}", data),
Err(_) => println!("Timeout!"),
}
}Testing using #[test] attributes and assert macros.
- Test attribute:
#[test] - Assertions:
assert_eq!,assert_ne! - Should panic:
#[should_panic] - Async tests:
#[tokio::test]
// Testing in Rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide() -> Result<(), String> {
let result = divide(10, 2)?;
assert_eq!(result, 5);
Ok(())
}
#[test]
#[should_panic(expected = "Division by zero")]
fn test_divide_by_zero() {
divide(10, 0).unwrap();
}
#[tokio::test]
async fn test_async() {
let result = async_function().await;
assert_eq!(result, "Success");
}
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
async fn async_function() -> &'static str {
"Success"
}Advanced closures with captured variables and iterator adapters.
- Move closures:
move || - Iterator adapters:
map,filter,fold - Any/All:
any(),all() - Closure traits:
Fn,FnMut,FnOnce
// Closures and Iterators Advanced
fn main() {
// Closure with captured variables
let factor = 2;
let multiply = |x: i32| x * factor;
println!("Multiply: {}", multiply(5));
// Closure with move
let numbers = vec![1, 2, 3];
let process = move |x: i32| {
numbers.iter().map(|&n| n * x).collect::<Vec<i32>>()
};
println!("Processed: {:?}", process(2));
// Iterator adapters
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: Vec<i32> = data
.iter()
.skip(2)
.take(5)
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("Result: {:?}", result);
// Fold (reduce)
let sum = data.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum);
// Any/All
let any_even = data.iter().any(|&x| x % 2 == 0);
let all_even = data.iter().all(|&x| x % 2 == 0);
println!("Any even: {}, All even: {}", any_even, all_even);
}Smart pointers in Rust: Box, Rc, Arc, RefCell, and Weak.
- Box: Heap allocation
- Rc: Reference counting (single-threaded)
- Arc: Atomic reference counting (multi-threaded)
- RefCell: Interior mutability
- Weak: Weak references to avoid cycles
// Smart Pointers in Rust
use std::rc::Rc;
use std::cell::RefCell;
// Rc (Reference Counting)
fn rc_example() {
let data = Rc::new(42);
let data2 = Rc::clone(&data);
let data3 = Rc::clone(&data);
println!("Rc count: {}", Rc::strong_count(&data));
println!("Value: {}", data);
}
// RefCell (Interior Mutability)
fn refcell_example() {
let data = RefCell::new(42);
*data.borrow_mut() = 100;
println!("Value: {}", data.borrow());
}
// Rc + RefCell combination
fn rc_refcell_example() {
let shared_data = Rc::new(RefCell::new(42));
let data2 = Rc::clone(&shared_data);
let data3 = Rc::clone(&shared_data);
*data2.borrow_mut() = 100;
println!("Value: {}", data3.borrow());
}
// Weak references
use std::rc::Weak;
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn weak_example() {
let parent = Rc::new(Node {
value: 1,
parent: RefCell::new(Weak::new()),
children: RefCell::new(Vec::new()),
});
let child = Rc::new(Node {
value: 2,
parent: RefCell::new(Rc::downgrade(&parent)),
children: RefCell::new(Vec::new()),
});
parent.children.borrow_mut().push(child);
println!("Parent value: {}", parent.value);
println!("Parent children count: {}", parent.children.borrow().len());
}
fn main() {
rc_example();
refcell_example();
rc_refcell_example();
weak_example();
}Lifetimes ensure references are valid and prevent dangling references.
- Lifetime annotations:
'a - Multiple lifetimes:
'a, 'b - Lifetime elision: Compiler can infer
- Static lifetime:
'static
// Lifetimes in Rust
// Lifetimes ensure references are valid
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// Struct with lifetimes
struct Person<'a> {
name: &'a str,
age: u32,
}
impl<'a> Person<'a> {
fn new(name: &'a str, age: u32) -> Self {
Person { name, age }
}
fn get_name(&self) -> &str {
self.name
}
}
// Multiple lifetimes
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
// Lifetime elision
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// Static lifetime
const STATIC_STR: &'static str = "This has a static lifetime";
fn main() {
let string1 = String::from("long string");
let string2 = "short";
let result = longest(&string1, string2);
println!("Longest: {}", result);
let name = String::from("Alice");
let person = Person::new(&name, 25);
println!("Person: {}", person.get_name());
let word = first_word("Hello World");
println!("First word: {}", word);
println!("Static string: {}", STATIC_STR);
}Unsafe Rust allows operations that the compiler can't verify, like raw pointers and FFI.
- Unsafe block:
unsafe - Raw pointers:
*const T,*mut T - FFI:
extern "C" - Safe abstractions: Wrapping unsafe in safe APIs
// Unsafe Rust
fn main() {
// Raw pointers
let x = 42;
let raw_ptr = &x as *const i32;
unsafe {
println!("Raw pointer value: {}", *raw_ptr);
}
// Mutable raw pointer
let mut y = 10;
let raw_mut_ptr = &mut y as *mut i32;
unsafe {
*raw_mut_ptr = 20;
}
println!("Modified value: {}", y);
// Calling unsafe function
unsafe fn dangerous() {
println!("This is an unsafe function");
}
unsafe {
dangerous();
}
// Using unsafe block for FFI
#[cfg(target_os = "windows")]
unsafe {
// Windows API calls
}
// Safe abstraction over unsafe
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
unsafe {
assert!(mid <= len);
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
let mut numbers = vec![1, 2, 3, 4, 5];
let (left, right) = split_at_mut(&mut numbers, 2);
println!("Left: {:?}, Right: {:?}", left, right);
}Foreign Function Interface for calling C functions and exposing Rust functions to C.
- extern "C": Declare C functions
- #[no_mangle]: Prevent name mangling
- repr(C): C-compatible struct layout
- CString: Convert between Rust and C strings
// FFI and C Interop
use std::ffi::CString;
use std::os::raw::c_char;
// Declare external C function
extern "C" {
fn printf(format: *const c_char, ...) -> i32;
}
// C-compatible struct
#[repr(C)]
struct Point {
x: f64,
y: f64,
}
// Export function to C
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn create_point(x: f64, y: f64) -> *mut Point {
Box::into_raw(Box::new(Point { x, y }))
}
#[no_mangle]
pub extern "C" fn free_point(ptr: *mut Point) {
if !ptr.is_null() {
unsafe { drop(Box::from_raw(ptr)); }
}
}
#[no_mangle]
pub extern "C" fn get_point_x(ptr: *const Point) -> f64 {
unsafe { (*ptr).x }
}
#[no_mangle]
pub extern "C" fn get_point_y(ptr: *const Point) -> f64 {
unsafe { (*ptr).y }
}
// Safe wrapper
fn call_printf() {
let format = CString::new("Hello from printf: %d\n").unwrap();
unsafe {
printf(format.as_ptr(), 42);
}
}
fn main() {
// Call C function
call_printf();
// Use exported function
let result = add_numbers(5, 3);
println!("Add numbers: {}", result);
// Use point functions
let point_ptr = create_point(10.0, 20.0);
unsafe {
println!("Point x: {}, y: {}", get_point_x(point_ptr), get_point_y(point_ptr));
free_point(point_ptr);
}
}