Content is user-generated and unverified.

Comprehensive Rust Reference Guide

📚 Table of Contents

  1. Basic Syntax & Style
  2. Ownership & Memory Management
  3. References & Borrowing
  4. Generics
  5. Traits
  6. Trait Bounds
  7. Generic vs Trait Objects
  8. Object Safety
  9. Data Structures
  10. Structs & Methods
  11. Control Flow
  12. Collections
  13. Strings
  14. Modules & Crates
  15. Error Handling
  16. Concurrency & Threading
  17. Smart Pointers
  18. Closures
  19. Common Traits
  20. Macros
  21. Database Programming
  22. Unsafe Code

Basic Syntax & Style

Formatting Rules

  • Indentation: Use 4 spaces, not tabs
  • Return values: The final expression in a function block is the return value (no semicolon needed)
  • Function location: Rust doesn't care where functions are defined, only that they're in a visible scope

Conditionals

  • if expressions: Must always have a Boolean condition (be explicit)
  • No automatic type coercion to boolean

Ownership & Memory Management

The Three Rules of Ownership

  1. Each value in Rust has an owner
  2. There can only be one owner at a time
  3. When the owner goes out of scope, the value is dropped

Memory Model Overview

┌─────────────────────────────────────────────────────────────┐
│                    RUST MEMORY MODEL                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  STACK                           HEAP                        │
│  ├─ Known, fixed size            ├─ Unknown/dynamic size    │
│  ├─ Fast access                  ├─ Slower access           │
│  ├─ Automatic management         ├─ Manual management       │
│  └─ Not allocating               └─ Requires allocation     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Stack vs Heap

  • Stack: Known, fixed size at compile time. Fast. Automatic cleanup.
  • Heap: Unknown/dynamic size. Requires allocation. Returns a pointer. Less organized.
  • Heap Safety: Less safe because data is accessible to all threads

The drop Function

  • Automatically called when a variable goes out of scope (at closing })
  • Returns memory to the allocator
  • No manual memory management needed (no garbage collector!)

Ownership in Action

rust
fn main() {
    let s = String::from("hello");  // s owns the String
    takes_ownership(s);              // s's value MOVES
                                     // s is no longer valid!

    let x = 5;                       // x owns the i32
    makes_copy(x);                   // i32 is Copy, so x is still valid
                                     // x can still be used

} // x goes out of scope, then s
  // Since s was moved, nothing happens for s

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_string dropped, memory freed

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
} // Nothing special happens (Copy type)

Copy vs Clone

FeatureCopyClone
How it worksImplicit bitwise copyExplicit method call .clone()
SyntaxAutomaticMust call .clone()
PerformanceAlways cheap (stack)Can be expensive (heap)
MemoryStack onlyStack or Heap
RequirementsMust also implement CloneCan exist alone

Types that are Copy

  • All integer types: i32, u64, etc.
  • Floating point: f32, f64
  • bool, char
  • Tuples and arrays (if all elements are Copy)
  • References: &T (but NOT &mut T)

Rules for Copy

  • Must be cheap to copy (stack only)
  • Cannot contain heap data (String, Vec, Box are NOT Copy)
  • Must also implement Clone
  • All fields must be Copy

What Ownership Solves

  • Tracking what code uses heap data
  • Minimizing duplicate data on heap
  • Cleaning up unused data (no memory leaks)
  • No garbage collector needed!

References & Borrowing

The Rules of References

  1. At any given time, you can have either:
    • One mutable reference (&mut T), OR
    • Any number of immutable references (&T)
  2. References must always be valid (no dangling references)

Mutable Reference Restriction

  • Big restriction: If you have a mutable reference to a value, you can have no other references to that value
  • Benefit: Prevents data races at compile time!

Data Race Conditions

A data race occurs when:

  1. Two or more pointers access the same data at the same time
  2. At least one pointer is writing to the data
  3. No synchronization mechanism exists

Rust prevents data races at compile time!

Reference Lifetime Guarantee

If you have a reference to data, the compiler ensures that the data will not go out of scope before the reference does.

rust
// Compiler prevents this:
let r;
{
    let x = 5;
    r = &x;  // ❌ ERROR: x doesn't live long enough
}
println!("{}", r);  // x is already dropped!

Generics

What are Generics?

Generics allow you to write code that works with multiple types while maintaining type safety and performance. Type parameters are declared using angle brackets <>.

Key Benefit: Zero runtime cost - compiler creates specialized code for each type (monomorphization).

Generic Functions

rust
// Basic generic function
fn print_value<T>(value: T) 
where
    T: std::fmt::Display
{
    println!("Value: {}", value);
}

// Usage
print_value(42);           // Works with i32
print_value("hello");      // Works with &str
print_value(3.14);         // Works with f64

Generic Structs

rust
// Single type parameter
struct Container<T> {
    value: T,
}

impl<T> Container<T> {
    fn new(value: T) -> Self {
        Container { value }
    }
    
    fn get(&self) -> &T {
        &self.value
    }
}

// Usage
let int_container = Container::new(42);
let string_container = Container::new(String::from("hello"));

Multiple Type Parameters

rust
struct Pair<T, U> {
    first: T,
    second: U,
}

impl<T, U> Pair<T, U> {
    fn new(first: T, second: U) -> Self {
        Pair { first, second }
    }
    
    fn get_first(&self) -> &T {
        &self.first
    }
    
    fn get_second(&self) -> &U {
        &self.second
    }
}

// Usage - different types for each field
let pair = Pair::new(42, "hello");
let another = Pair::new(String::from("world"), 3.14);

Generic Enums

rust
// Option-like enum
enum MyOption<T> {
    Some(T),
    None,
}

// Result-like enum
enum MyResult<T, E> {
    Ok(T),
    Err(E),
}

// Usage
let some_number: MyOption<i32> = MyOption::Some(5);
let no_number: MyOption<i32> = MyOption::None;

Generic Methods with Specific Implementations

rust
struct Point<T> {
    x: T,
    y: T,
}

// Generic implementation for all types
impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}

// Specific implementation only for f64
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

// Usage
let int_point = Point::new(5, 10);
let float_point = Point::new(3.0, 4.0);
println!("Distance: {}", float_point.distance_from_origin()); // Only works with f64

Real-World Example: Generic Stack

rust
struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Self {
        Stack { items: Vec::new() }
    }
    
    fn push(&mut self, item: T) {
        self.items.push(item);
    }
    
    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }
    
    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
    
    fn size(&self) -> usize {
        self.items.len()
    }
}

// Usage with different types
let mut int_stack = Stack::new();
int_stack.push(1);
int_stack.push(2);
println!("{:?}", int_stack.pop()); // Some(2)

let mut string_stack = Stack::new();
string_stack.push(String::from("hello"));
string_stack.push(String::from("world"));
println!("{:?}", string_stack.pop()); // Some("world")

Traits

What are Traits?

Traits define shared behavior - like interfaces in other languages. They specify a set of methods that types must implement.

Defining and Implementing Traits

rust
// Define a trait
trait Describe {
    fn describe(&self) -> String;
}

// Implement for different types
struct Dog {
    name: String,
}

impl Describe for Dog {
    fn describe(&self) -> String {
        format!("A dog named {}", self.name)
    }
}

struct Cat {
    name: String,
}

impl Describe for Cat {
    fn describe(&self) -> String {
        format!("A cat named {}", self.name)
    }
}

// Usage
let dog = Dog { name: String::from("Buddy") };
let cat = Cat { name: String::from("Whiskers") };
println!("{}", dog.describe());
println!("{}", cat.describe());

Default Implementations

rust
trait Summary {
    fn summarize_author(&self) -> String;
    
    // Default implementation
    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

struct Article {
    author: String,
    content: String,
}

impl Summary for Article {
    fn summarize_author(&self) -> String {
        self.author.clone()
    }
    // Uses default summarize() implementation
}

let article = Article {
    author: String::from("John"),
    content: String::from("Rust is great!"),
};
println!("{}", article.summarize());

Trait Bounds

Basic Trait Bounds

rust
// Function that works with any type implementing Display
fn print_it<T: std::fmt::Display>(value: T) {
    println!("{}", value);
}

// Multiple trait bounds using +
fn process<T: Clone + std::fmt::Display>(value: T) {
    let copy = value.clone();
    println!("{}", copy);
}

Where Clauses (Cleaner Syntax)

rust
// Instead of this:
fn complex<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
    // ...
}

// Use where clause for better readability:
fn complex<T, U>(t: T, u: U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Example: Finding the Largest

rust
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    
    largest
}

// Usage
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest: {}", largest(&numbers));

let chars = vec!['y', 'm', 'a', 'q'];
println!("Largest: {}", largest(&chars));

Generic vs Trait Objects

Generics (Static Dispatch)

Pros: Fast, compiler optimizes, can inline
Cons: Code size increases (monomorphization), can't mix types

rust
trait Animal {
    fn make_sound(&self) -> String;
}

struct Dog;
struct Cat;

impl Animal for Dog {
    fn make_sound(&self) -> String {
        "Woof!".to_string()
    }
}

impl Animal for Cat {
    fn make_sound(&self) -> String {
        "Meow!".to_string()
    }
}

// Generic function - compiler creates separate code for each type
fn animal_sound<T: Animal>(animal: &T) -> String {
    animal.make_sound()
}

// Usage - each type known at compile time
let dog = Dog;
let cat = Cat;
println!("{}", animal_sound(&dog));
println!("{}", animal_sound(&cat));

// ❌ Cannot mix types in a collection
// let animals = vec![Dog, Cat]; // Error!

Trait Objects (Dynamic Dispatch)

Pros: Can mix types, smaller code size, flexible
Cons: Slight runtime overhead (vtable lookup), can't inline

rust
// Function using trait object
fn animal_sound_dyn(animal: &dyn Animal) -> String {
    animal.make_sound()
}

// ✅ CAN mix types in collections!
let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog),
    Box::new(Cat),
    Box::new(Dog),
];

for animal in animals {
    println!("{}", animal.make_sound());
}

Real-World Example: GUI System

rust
trait Widget {
    fn draw(&self);
}

struct Button {
    label: String,
}

struct TextBox {
    content: String,
}

impl Widget for Button {
    fn draw(&self) {
        println!("Drawing button: {}", self.label);
    }
}

impl Widget for TextBox {
    fn draw(&self) {
        println!("Drawing textbox: {}", self.content);
    }
}

// With trait objects - can store mixed types!
struct Screen {
    components: Vec<Box<dyn Widget>>,
}

impl Screen {
    fn new() -> Self {
        Screen { components: Vec::new() }
    }
    
    fn add_component(&mut self, component: Box<dyn Widget>) {
        self.components.push(component);
    }
    
    fn render(&self) {
        for component in &self.components {
            component.draw();
        }
    }
}

// Usage
let mut screen = Screen::new();
screen.add_component(Box::new(Button { label: "Submit".to_string() }));
screen.add_component(Box::new(TextBox { content: "Email".to_string() }));
screen.add_component(Box::new(Button { label: "Cancel".to_string() }));
screen.render();

Comparison Table

FeatureGeneric <T: Trait>Trait Object dyn Trait
DispatchStatic (compile-time)Dynamic (runtime)
PerformanceFaster (no vtable)Slightly slower
Code sizeLarger (copies for each type)Smaller
Mixed types❌ No✅ Yes
Inline-able✅ Yes❌ No
Trait boundsMultiple traits OKLimited (object-safe only)

When to Use Each

Use Generics when:

  • Performance is critical
  • Types known at compile time
  • Working with single type at a time
  • Need aggressive compiler optimization

Use Trait Objects when:

  • Need to store mixed types in collections
  • Types determined at runtime
  • Building plugin systems
  • Code size matters more than speed

Object Safety

What is Object Safety?

A trait is "object-safe" if it can be used as a trait object (dyn Trait). Not all traits can be trait objects!

Rules for Object Safety

❌ Rule 1: No Generic Methods

rust
trait Container {
    fn add<T>(&mut self, item: T);  // Generic method - NOT object safe!
}

// This won't compile:
// let container: Box<dyn Container> = Box::new(MyContainer);
// Error: trait cannot be made into an object

// ✅ Solution: Make the trait generic instead
trait Container<T> {
    fn add(&mut self, item: T);  // No longer generic method
}

let container: Box<dyn Container<i32>> = Box::new(IntContainer);  // Works!

Why? The compiler can't create a vtable with infinite generic variations.

❌ Rule 2: No Methods Returning Self

rust
trait Cloneable {
    fn clone_me(&self) -> Self;  // Returns Self - NOT object safe!
}

// This won't compile:
// let obj: Box<dyn Cloneable> = Box::new(MyStruct);
// Error: method returns `Self`

// ✅ Solution: Return a trait object
trait Cloneable {
    fn clone_box(&self) -> Box<dyn Cloneable>;
}

impl Cloneable for MyStruct {
    fn clone_box(&self) -> Box<dyn Cloneable> {
        Box::new(MyStruct { value: self.value })
    }
}

let obj: Box<dyn Cloneable> = Box::new(MyStruct { value: 42 });
let cloned = obj.clone_box();  // Works!

Why? With type erasure, the compiler doesn't know what concrete type to return.

❌ Rule 3: No Associated Functions (without self)

rust
trait Factory {
    fn create() -> Self;  // No self parameter - NOT object safe!
}

// This won't compile:
// let factory: Box<dyn Factory> = Box::new(Product);
// Error: associated function has no `self` parameter

// ✅ Solution: Add a self parameter
trait Factory {
    fn create(&self) -> Box<dyn Factory>;  // Now has &self
}

let factory: Box<dyn Factory> = Box::new(ProductFactory);
let new_factory = factory.create();  // Works!

Why? Associated functions are called on the type itself, but trait objects erase the type.

❌ Rule 4: No where Self: Sized

rust
trait MyTrait {
    fn method(&self) where Self: Sized;  // NOT object safe!
}

Why? Trait objects are !Sized (unsized), which contradicts the Sized requirement.

Object Safety Checklist

A trait is object-safe if ALL methods:

  • ✅ Have a self parameter (&self, &mut self, or self)
  • ✅ Don't return Self (unless wrapped in Box<dyn Trait>)
  • ✅ Aren't generic
  • ✅ Don't have where Self: Sized

Real Example: Clone Trait

rust
// The standard Clone trait is NOT object safe
pub trait Clone {
    fn clone(&self) -> Self;  // Returns Self!
}

// ❌ This doesn't work:
// let obj: Box<dyn Clone> = Box::new(String::from("hello"));

// ✅ Object-safe alternative:
trait CloneBox {
    fn clone_box(&self) -> Box<dyn CloneBox>;
}

impl<T: Clone + 'static> CloneBox for T {
    fn clone_box(&self) -> Box<dyn CloneBox> {
        Box::new(self.clone())
    }
}

struct Dog {
    name: String,
}

impl Clone for Dog {
    fn clone(&self) -> Self {
        Dog { name: self.name.clone() }
    }
}

let dog: Box<dyn CloneBox> = Box::new(Dog { name: "Buddy".to_string() });
let cloned = dog.clone_box();  // Works!

Data Structures

Stack vs Heap Structures

Stack-allocated (Copy)        Heap-allocated (Clone required)
├─ Primitives (i32, f64)      ├─ String
├─ Arrays [T; N]              ├─ Vec<T>
├─ Tuples (T, U)              ├─ Box<T>
└─ References &T              ├─ HashMap<K, V>
                              └─ Custom structs with heap data

Choosing Data Structures

Use Vec<T> when:

  • Dynamic array needed
  • All values are the same type
  • Values stored next to each other in memory

Use HashMap<K, V> when:

  • Associate arbitrary keys with values
  • Need a cache
  • Want a map with no extra functionality

Use BTreeMap<K, V> when:

  • Need to find smallest/largest key-value pair
  • Want to find keys larger/smaller than something
  • Need all entries in sorted order
  • Want a map sorted by its keys

Note: In theory, BST is optimal (log₂n lookups), but in practice, it's inefficient for modern computer architectures due to cache locality issues.


Structs & Methods

Struct Mutability

  • Important: The entire struct instance must be mutable
  • Rust does NOT allow marking only certain fields as mutable
rust
struct User {
    username: String,
    email: String,
}

let mut user1 = User {  // Entire struct is mutable
    username: String::from("user"),
    email: String::from("user@example.com"),
};

user1.email = String::from("new@example.com");  // ✅ OK

Struct Update Syntax

rust
let user2 = User {
    email: String::from("another@example.com"),
    ..user1  // Copy remaining fields from user1
};

Methods vs Associated Functions

Methods

  • Take self, &self, or &mut self as first parameter
  • Called on an instance: instance.method()

Associated Functions

  • Don't have self as first parameter
  • Called on the type: Type::function()
  • Often used as constructors (commonly named new)
rust
impl Rectangle {
    // Associated function (constructor)
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
    
    // Method
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

// Usage
let sq = Rectangle::square(10);  // Associated function
let area = sq.area();             // Method

Taking Ownership with self

  • Using just self (not &self) takes ownership
  • Rare technique
  • Used when transforming self into something else
  • Prevents caller from using original instance after transformation

Control Flow

Loop Labels

  • Use loop labels for nested loops
  • Apply break or continue to specific loops
  • Labels must begin with a single quote (')
rust
'outer: loop {
    loop {
        break 'outer;  // Breaks the outer loop
    }
}

if let - Concise Control Flow

  • Combines if and let for pattern matching
  • Less verbose than full match when you only care about one case
rust
let some_value = Some(3);

// Instead of:
match some_value {
    Some(3) => println!("three"),
    _ => (),
}

// Use:
if let Some(3) = some_value {
    println!("three");
}

Collections

Vectors (Vec<T>)

Key Properties

  • Store variable number of values
  • All values must be same type
  • Values stored next to each other in memory

Creating Vectors

rust
let mut v = Vec::new();
let v = vec![1, 2, 3];  // vec! macro
let mut v = vec![100, 32, 57];

Modifying Vectors

rust
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;  // Dereference and modify
}

Important Memory Consideration

  • Adding elements may require reallocation
  • If not enough space, allocates new memory and copies old elements
  • References to elements become invalid after reallocation
  • Borrowing rules prevent this bug at compile time

Restriction in Loops

rust
let mut v = vec![1, 2, 3];

for i in &v {
    v.push(4);  // ❌ ERROR: Can't modify while iterating
}

The reference held by the for loop prevents simultaneous modification.

Hash Maps

Use Cases

  • Associate arbitrary keys with arbitrary values
  • Implement a cache
  • Need a map with no extra functionality

Hash Sets

  • Store unique keys only (no values)
  • Similar to HashMap but key-only

Strings

String Types

TypeDescriptionLocationMutable
StringGrowable, heap-allocatedHeapYes
&strString slice (view into UTF-8 bytes)Can be anywhereNo

String Literals

rust
let s = "Hello, world!";  // Type: &str
  • String literals are &str (immutable references)
  • Stored inside the binary
  • Point to a specific location in the binary

Creating Strings

rust
let s = String::new();
let s = String::from("hello");
let s = "hello".to_string();

Why No Indexing?

rust
let s = String::from("hello");
let h = s[0];  // ❌ ERROR: Cannot index into String

Reason: Indexing operations should be O(1), but Rust can't guarantee this with UTF-8 strings. Rust would need to walk through from the beginning to validate character boundaries.


Modules & Crates

Crate Types

Library Crate

  • No main function
  • Doesn't compile to executable
  • Defines functionality for other projects
  • Example: rand crate

Note: When Rustaceans say "crate", they usually mean library crate.

Binary Crate

  • Has main function
  • Compiles to executable

Module Declaration

Declare modules in the crate root file:

rust
mod garden;  // Compiler looks for module code in:
  1. Inline: mod garden { ... }
  2. In file: src/garden.rs
  3. In file: src/garden/mod.rs

Crates.io

  • Central Rust package registry
  • Publish and share crates
  • Search for reusable libraries

Error Handling

The Result<T, E> Type

rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}

unwrap() Method

  • Shortcut for match expression
  • If Result is Ok(value): returns value
  • If Result is Err: calls panic! macro
rust
let f = File::open("hello.txt").unwrap();
// If file doesn't exist, program panics

Note: Can be verbose and doesn't always communicate intent well. Consider using more specific helper methods or proper error handling.


Concurrency & Threading

Core Concepts

  1. Creating threads: Run multiple pieces of code simultaneously
  2. Message-passing: Channels send messages between threads
  3. Shared-state: Multiple threads access shared data
  4. Send and Sync traits: Extend concurrency guarantees

Thread Lifecycle

Important: When the main thread completes, all spawned threads are shut down, whether or not they finished running.

The Send Trait

  • Marker trait
  • Indicates ownership can be transferred between threads
  • Types implementing Send: String, Vec<T>, most primitives

The Sync Trait

  • Marker trait
  • Indicates it's safe to reference from multiple threads
  • Types implementing Sync: Arc<T>, Mutex<T>

Trait Comparison

TraitConceptTypical ExampleOpposite Example
SendOwnership can move across threadsString, Vec<T>Rc<T>
SyncShared reference &T can be used across threadsArc<T>, Mutex<T>RefCell<T>

Thread Example with Closures

rust
use std::thread;

let data = vec![1, 2, 3];

// `move` keyword transfers ownership to thread
thread::spawn(move || {
    println!("{:?}", data);
});

Smart Pointers

Box<T> - Heap Allocation

  • Stores data on the heap
  • Stack stores pointer to heap data

Rc<T> - Reference Counting (Single-threaded)

Purpose: Shared ownership in single-threaded scenarios

rust
use std::rc::Rc;

let a = Rc::new(5);
let b = Rc::clone(&a);  // Increments reference count
let c = Rc::clone(&a);  // Now 3 owners

Why needed? Allows multiple parts of program to own the same data.

Example use case: Doubly-linked list where nodes need to be referenced by multiple other nodes.

Arc<T> - Atomic Reference Counting (Multi-threaded)

Purpose: Thread-safe shared ownership

rust
use std::sync::Arc;

let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);

std::thread::spawn(move || {
    println!("{:?}", data_clone);
});

When to use: When you need Rc but across multiple threads.

RefCell<T> - Interior Mutability (Single-threaded)

Purpose: Mutable borrows checked at runtime instead of compile time

rust
use std::cell::RefCell;

let x = RefCell::new(5);
*x.borrow_mut() += 1;  // Mutable borrow

Why needed? Allows mutation through shared references when compiler can't prove safety at compile time.

Mutex<T> - Mutual Exclusion (Multi-threaded)

Purpose: Thread-safe interior mutability

rust
use std::sync::Mutex;

let m = Mutex::new(5);
{
    let mut num = m.lock().unwrap();
    *num = 6;
}  // Lock is released here

Key Properties:

  • Only one thread can hold the lock at a time
  • Prevents data races
  • Blocks other threads until lock is available

Atomic Types - Lock-free Concurrency

Purpose: Indivisible operations that are thread-safe without locks

Available types: AtomicBool, AtomicIsize, AtomicUsize, AtomicPtr, etc.

Guarantee: Operations are not susceptible to race conditions or data corruption.


LRU Cache Implementation Guide

Core Structure

  • Doubly-linked list: Track recency of access
  • Tail: Most recently used
  • Head: Least recently used (evict from here)

Key Trick: Dummy Nodes

Create dummy head and tail nodes and connect them together. This simplifies edge case handling.

Helper Functions

Consider these for clarity:

  1. remove(node): Remove node from list
  2. insert(node): Insert node at tail (most recent)

Strategy

  • For get(): Remove node, then insert at tail (even if already there)
  • For put(): Insert new/updated node, then remove LRU if capacity exceeded

Single-threaded Implementation

rust
use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    key: i32,
    value: i32,
    prev: Option<Rc<RefCell<Node>>>,
    next: Option<Rc<RefCell<Node>>>,
}

Why Rc<RefCell<Node>>?

ComponentPurposeWhat it provides
RcShared ownershipMultiple nodes reference same node
RefCellInterior mutabilityModify through shared references

Why Both Are Needed

Rc solves shared ownership:

rust
// Without Rc - doesn't work!
struct Node {
    prev: Option<Box<Node>>,  // ❌ Box owns exclusively
}

// Node A can't be owned by both Node B's prev AND Node C's next

RefCell solves mutability:

rust
// Without RefCell - doesn't work!
fn remove_node(node: Rc<Node>) {
    node.prev = None;  // ❌ Can't mutate through Rc
}

// With RefCell - works!
fn remove_node(node: Rc<RefCell<Node>>) {
    node.borrow_mut().prev = None;  // ✅ Runtime-checked mutation
}

Multi-threaded Implementation

rust
use std::sync::{Arc, Mutex};

struct Node {
    key: i32,
    value: i32,
    prev: Option<Arc<Mutex<Node>>>,
    next: Option<Arc<Mutex<Node>>>,
}

Why Arc<Mutex<Node>>?

ComponentPurposeWhat it provides
ArcThread-safe shared ownershipMultiple threads reference same node
MutexThread-safe interior mutabilityOnly one thread accesses at a time

Example Implementation

rust
pub fn remove(&self, node: Arc<Mutex<Node>>) {
    let prev = node.lock().unwrap().prev.take().unwrap();
    let next = node.lock().unwrap().next.take().unwrap();
    
    prev.lock().unwrap().next = Some(Arc::clone(&next));
    next.lock().unwrap().prev = Some(Arc::clone(&prev));
}

Choosing the Right Combination

Single-threaded                Multi-threaded
└─ Rc<RefCell<T>>             └─ Arc<Mutex<T>>
   ├─ Shared ownership            ├─ Thread-safe ownership
   └─ Runtime borrow checking     └─ Thread-safe mutation

Closures

What Are Closures?

Anonymous functions that can capture their environment.

Basic Syntax

rust
// Regular function
fn add_one(x: i32) -> i32 {
    x + 1
}

// Closure (types can be inferred)
let add_one = |x: i32| x + 1;
let add_one = |x| x + 1;  // Type inferred

// Using it
let result = add_one(5);  // 6

Capturing Variables

rust
let multiplier = 10;

// Closure captures 'multiplier' from surrounding scope
let multiply = |x| x * multiplier;

println!("{}", multiply(5));  // 50

Key difference from functions: Closures can capture variables from their environment.

Capture Modes

rust
let s = String::from("hello");

// 1. Borrows immutably (Fn)
let print = || println!("{}", s);

// 2. Borrows mutably (FnMut)
let mut count = 0;
let mut increment = || count += 1;

// 3. Takes ownership (FnOnce)
let consume = || drop(s);  // Moves 's' into closure

Closure Traits

TraitBehaviorCan call multiple times?
FnBorrows immutably✅ Yes
FnMutBorrows mutably✅ Yes
FnOnceTakes ownership❌ No (only once)

Common Use Cases

With Iterators

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

// Filter even numbers
let evens: Vec<_> = numbers.iter()
    .filter(|&x| x % 2 == 0)
    .collect();

// Map to double each
let doubled: Vec<_> = numbers.iter()
    .map(|x| x * 2)
    .collect();

As Callbacks

rust
fn do_twice<F>(f: F) 
where
    F: Fn(i32) -> i32
{
    println!("{}", f(5));
    println!("{}", f(10));
}

do_twice(|x| x * 2);
// Output: 10, 20

With Threads

rust
use std::thread;

let data = vec![1, 2, 3];

// 'move' keyword transfers ownership
thread::spawn(move || {
    println!("{:?}", data);
});

Common Traits

Display and Debug

rust
use std::fmt;

struct Person {
    name: String,
    age: u32,
}

// Display - for user-facing output
impl fmt::Display for Person {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} (age {})", self.name, self.age)
    }
}

// Debug - for debugging (can also use #[derive(Debug)])
impl fmt::Debug for Person {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Person")
            .field("name", &self.name)
            .field("age", &self.age)
            .finish()
    }
}

let person = Person { name: "Alice".to_string(), age: 30 };
println!("{}", person);    // Uses Display
println!("{:?}", person);  // Uses Debug

Display Trait (std::fmt::Display)

Purpose: Format trait for user-facing output ({} in format strings)

Benefits:

  • Implementing Display automatically implements ToString
  • Enables .to_string() method
  • Prefer implementing Display over implementing ToString directly

Key Difference:

  • Display: User-facing output
  • Debug: Developer-facing output (can be derived with #[derive(Debug)])

Clone and Copy

rust
// Clone - explicit duplication
#[derive(Clone)]
struct Data {
    value: String,
}

let data1 = Data { value: "hello".to_string() };
let data2 = data1.clone();  // Explicit clone
println!("{}", data1.value);  // data1 still valid

// Copy - implicit duplication (only for stack-only types)
#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = p1;  // Implicit copy
println!("{}", p1.x);  // p1 still valid (was copied, not moved)

Rules for Copy:

  • Must be cheap to copy (stack-only data)
  • Cannot contain heap data (no String, Vec, Box)
  • Must also implement Clone

PartialEq and Eq

rust
#[derive(PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{}", p1 == p2);  // true

// Custom implementation
impl PartialEq for Person {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.age == other.age
    }
}

PartialOrd and Ord

rust
#[derive(PartialEq, PartialOrd)]
struct Score(i32);

let s1 = Score(100);
let s2 = Score(200);
println!("{}", s1 < s2);  // true

Derivable Traits

rust
// Most common derivable traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyStruct {
    value: i32,
}

Macros

Two Types of Macros

1. Declarative Macros (macro_rules!)

  • Match patterns in Rust code
  • Generate new code using those patterns
rust
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

2. Procedural Macros

  • Generate code at compile time through the syntax tree
  • Defined within their own crates
  • Invoked through custom attributes
rust
#[derive(Debug)]  // Procedural macro
struct MyStruct;

Database Programming

Popular Rust Database Libraries

Diesel

  • Popular ORM (Object-Relational Mapping)
  • Type-safe, composable query builder
  • Supports: PostgreSQL, MySQL, SQLite

Postgres

  • Native Rust library for PostgreSQL
  • Safe, ergonomic API

SQLx

  • Unified API for multiple databases
  • Supports: PostgreSQL, MySQL, SQLite
  • Both sync and async operations
  • Type-safe query builder

Why Rust for Databases?

  • High performance
  • Memory safety
  • Type safety prevents SQL injection
  • Zero-cost abstractions

Unsafe Code

Why Unsafe Code Exists

Rust is designed for safety, but sometimes you need low-level memory operations that the compiler can't verify as safe.

Managing Unsafe Code

1. Raw Pointers

  • Similar to C pointers
  • Allow pointer arithmetic and casting
rust
let x = 5;
let raw = &x as *const i32;

unsafe {
    println!("{}", *raw);
}

2. Unsafe Functions

  • Marked with unsafe keyword
  • Developer must manually ensure correct usage
rust
unsafe fn dangerous() {
    // Low-level operations
}

3. Unsafe Blocks

  • Mark a block as unsafe
  • Must be contained within a safe function
  • Compiler ensures overall program behavior is safe
rust
fn safe_function() {
    unsafe {
        // Unsafe operations here
    }
    // Safe operations continue
}

Safety Guarantees

  • Unsafe code is isolated in unsafe blocks
  • Compiler verifies safe code around unsafe blocks
  • Reduces surface area for bugs

Testing

The rustc_test Framework

Built-in unit testing system:

  • Define tests with #[test] attribute
  • Assertion macros: assert_eq!, assert_ne!
  • Easy to write and maintain tests
rust
#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
fn it_fails() {
    panic!("This test will fail");
}

Key Methods & Functions

collect()

  • Takes anything iterable
  • Turns it into a relevant collection
  • Type inference determines the collection type
  • One of the most powerful methods in the standard library
rust
let v: Vec<i32> = (0..10).collect();
let s: String = vec!['h', 'i'].into_iter().collect();
let map: HashMap<_, _> = vec![(1, 2), (3, 4)].into_iter().collect();

Quick Reference: Common Patterns

Variable Naming Conventions

Use concise names in closures and iterators:

  • i, j for indices
  • e for event
  • el for element
  • x, y for generic values

Common Iteration Patterns

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

// Immutable iteration
for item in &v {
    println!("{}", item);
}

// Mutable iteration
for item in &mut v {
    *item += 1;
}

// Consuming iteration
for item in v {
    // v is moved, can't use v after this
}

Type Annotations with collect()

rust
// Explicit type
let v: Vec<i32> = iter.collect();

// Turbofish syntax
let v = iter.collect::<Vec<i32>>();

// Underscore for partial inference
let v: Vec<_> = iter.collect();

Best Practices

1. Prefer Generics for Performance

rust
// Fast - compiler optimizes
fn process<T: Display>(item: T) {
    println!("{}", item);
}

2. Use Trait Objects for Flexibility

rust
// Flexible - can store different types
let items: Vec<Box<dyn Display>> = vec![
    Box::new(42),
    Box::new("hello"),
];

3. Use Where Clauses for Readability

rust
// More readable
fn complex<T, U>(t: T, u: U)
where
    T: Display + Clone,
    U: Debug + Clone,
{
    // ...
}

4. Implement Common Traits

rust
// Makes your types more usable
#[derive(Debug, Clone, PartialEq)]
struct MyType {
    // ...
}

5. Check Object Safety Early

rust
// Try to create a trait object to check
let obj: Box<dyn MyTrait> = ...;
// Compiler will tell you if it's not object-safe

Memory Safety Guarantees Summary

What Rust Prevents

✅ Null pointer dereferences
✅ Data races
✅ Buffer overflows
✅ Use-after-free
✅ Double-free
✅ Iterator invalidation

How Rust Prevents Them

  • Ownership system: One owner, automatic cleanup
  • Borrowing rules: Either multiple readers OR one writer
  • Lifetime tracking: References never outlive their data
  • Compile-time checks: Catch errors before running
  • No garbage collector needed: Zero runtime overhead

Additional Resources


This guide contains all your original content plus comprehensive generics and traits coverage, organized for easy review and understanding. Use the table of contents to jump to specific sections!

Content is user-generated and unverified.
    Complete Rust Programming Reference Guide 2024 | Claude