 Command

Pranesh Nikhar's personal site. Vim-style keybinds for navigation; theme + font pickers below.

Theme
 Font Body Code
Reader
Keybinds
Navigation
j / ↓ Next item k / ↑ Previous item g First item in region G Last item in region zz Center focused item h / l Move left/right region ] / [ Next/previous heading } / { Next/previous block d / u Half-page down/up
Layout
<zh> / <zl> Toggle left/right sidebar <zr> Toggle reader view <zj> / <zk> Focus main/navbar <S-h/j/k/l> Focus left/main/navbar/right ⌃H / ⌃L Focus left/right sidebar ⌃J / ⌃K Focus main/navbar ⇧C / ⇧E Collapse / expand all sections
Dialogs
⌃P / : Command palette ⌃X Theme picker / Search ? Show keybinds Esc / ⌃C Close dialog
History
n Next document b Previous document ⌃O History back ⌃I History forward
 Search
about: Pranesh Nikhar about/more: πŸͺͺ More docs/test: Docs Test ideas: πŸ’‘ Ideas more: βž• More now: Now posts: πŸ“¬ Posts projects: πŸ“š Projects webtui: Style posts/agentic-eda: πŸ“Š AgenticEDA β€” Automated Exploratory Data Analysis with LangGraph posts/cap-theorem-outage-story: 🌐 CAP Theorem with a Real Outage Story posts/codepilot: ✈️ CodePilot β€” From Requirements to Deployable FastAPI Backend posts/common-auth-mistakes: πŸ” Common Auth Mistakes Developers Make posts/compiled-vs-jit-vs-interpreted: ⚑ Why Is X Language Fast or Slow? β€” Compiled vs JIT vs Interpreted posts/cs-degree-gaps: πŸŽ“ Things CS Degrees Don't Teach You posts/cve-2025-breach-analysis: πŸ›‘οΈ CVE-2025 Breach Analysis β€” Midnight Blizzard and the 16 Billion Credential Leak posts/fixloop: πŸ”„ FixLoop β€” AI Agent Loop for Self-Correcting Code posts/functional-vs-oop: ⚑ Functional vs OOP β€” Same Problem, Both Ways posts/getman: 🦾 Getman β€” Declarative API Tester for CLI & TUI posts/how-compilers-optimize: βš™οΈ How Compilers Actually Optimize Your Code posts/http3-quic: ⚑ HTTP/3 and QUIC β€” Why They Matter posts/leetcode-vs-engineering: 🧩 LeetCode vs Real Engineering Skills posts/llm-from-scratch: 🧠 LLM from Scratch β€” GPT-Style Transformer in PyTorch posts/lsm-trees-bloom-filters: 🌳 LSM Trees & Bloom Filters β€” Production Deep Dive posts/mcp-workflow-builder: πŸ”§ MCP Workflow Builder β€” Visual DAG for MCP Tools posts/persistent-memory: 🧠 Persistent Memory β€” Long-Term Memory for AI Agents via MCP posts/playcli: 🎬 PlayCLI β€” Terminal Video Player posts/postgres-mvcc: πŸ—„οΈ How PostgreSQL MVCC Works β€” Multi-Version Concurrency Control Deep Dive posts/raft-consensus: β›΅ Raft Consensus Algorithm Explained posts/rust-borrow-checker: πŸ¦€ Rust Borrow Checker β€” Catches Real Bugs posts/titan: πŸ€– Titan β€” Terminal AI Coding Agent posts/what-happens-url: 🌐 What Happens Between Typing a URL and Seeing the Page posts/what-happens-when-you-run-a-program: βš™οΈ What Actually Happens When You Run a Program posts/zero-knowledge-proofs: πŸ” Zero-Knowledge Proofs Explained Simply webtui/components/accordion: Accordion webtui/components/badge: Badge webtui/components/button: Button webtui/components/checkbox: Checkbox webtui/components/dialog: Dialog webtui/components/input: Input webtui/components/popover: Popover webtui/components/pre: Pre webtui/components/progress: Progress webtui/components/radio: Radio webtui/components/range: Range webtui/components/separator: Separator webtui/components/spinner: Spinner webtui/components/switch: Switch webtui/components/table: Table webtui/components/textarea: Textarea webtui/components/tooltip: Popover webtui/components/typography: Typography webtui/components/view: View webtui/contributing/contributing: Contributing webtui/contributing/contributing: ## Local Development webtui/contributing/contributing: ## Issues webtui/contributing/contributing: ## Pull Requests webtui/contributing/style-guide: Style Guide webtui/contributing/style-guide: ## CSS Units webtui/contributing/style-guide: ## Selectors webtui/contributing/style-guide: ## Documentation webtui/installation/astro: Astro webtui/installation/astro: ## Scoping webtui/installation/astro: ### Frontmatter Imports webtui/installation/astro: ### β€Ήstyleβ€Ί tag webtui/installation/astro: ### Full Library Import webtui/installation/nextjs: Next.js webtui/installation/vite: Vite webtui/plugins/plugin-dev: Developing Plugins webtui/plugins/plugin-dev: ### Style Layers webtui/plugins/plugin-nf: Nerd Font Plugin webtui/plugins/theme-catppuccin: Catppuccin Theme webtui/plugins/theme-custom: Custom Theme webtui/plugins/theme-everforest: Everforest Theme webtui/plugins/theme-gruvbox: Gruvbox Theme webtui/plugins/theme-nord: Nord Theme webtui/plugins/theme-vitesse: Vitesse Theme webtui/start/ascii-boxes: ASCII Boxes webtui/start/changelog: Changelog webtui/start/installation: Installation webtui/start/installation: ## Installation webtui/start/installation: ## Using CSS webtui/start/installation: ## Using ESM webtui/start/installation: ## Using a CDN webtui/start/installation: ## Full Library Import webtui/start/installation: ### CSS webtui/start/installation: ### ESM webtui/start/installation: ### CDN webtui/start/intro: Introduction webtui/start/intro: ## Features webtui/start/plugins: Plugins webtui/start/plugins: ## Official Plugins webtui/start/plugins: ### Themes webtui/start/plugins: ## Community Plugins webtui/start/theming: Theming webtui/start/theming: ## CSS Variables webtui/start/theming: ### Font Styles webtui/start/theming: ### Colors webtui/start/theming: ### Light & Dark webtui/start/theming: ## Theme Plugins webtui/start/theming: ### Using Multiple Theme Accents webtui/start/tuis-vs-guis: TUIs vs GUIs webtui/start/tuis-vs-guis: ## Monospace Fonts webtui/start/tuis-vs-guis: ## Character Cells
 Theme Current: Light j/k or ↑/↓ + Enter

πŸ¦€ Rust Borrow Checker β€” Catches Real Bugs

The borrow checker isn't just a compiler pedant β€” it prevents the C++ bugs that wake you at 3 AM. Iterator invalidation, use-after-free, and data races, caught at compile time.

🎯 The Three Rules

Rust’s ownership system reduces to three rules:

  1. Each value has exactly one owner at any time
  2. References must never outlive the data they point to
  3. At any moment, you have either one mutable reference or any number of immutable references (never both)

These rules are enforced entirely at compile time. No runtime overhead, no garbage collector, no null pointer checks.

// Rule 1: One owner
let s = String::from("hello");
let t = s;           // s moves to t
// println!("{s}");  // ❌ Compile error! s is no longer valid

// Rule 3: XOR of mutability
let mut v = Vec::new();
let r1 = &v;
let r2 = &v;
// let r3 = &mut v;  // ❌ Cannot borrow as mutable while immutable refs exist
println!("{r1} {r2}"); // βœ… Immutable references coexist fine

πŸ› Bug 1: Iterator Invalidation

The C++ Version (Compiles, Crashes at 3AM)

#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};

    for (auto it = v.begin(); it != v.end(); ++it) {
        if (*it % 2 == 0) {
            v.push_back(*it * 10);  // Mutates v while iterating!
        }
    }
    // Undefined behavior: iterator may be invalidated
    return 0;
}

This compiles with zero warnings at -Wall -Wextra. What happens at runtime?

  • push_back may trigger reallocation when capacity is exceeded
  • After reallocation, it points to freed memory
  • The comparison it != v.end() and the increment ++it operate on dangling pointers
  • Demonstrably, this may: segfault, corrupt memory, silently produce wrong results, or work by coincidence until the vector grows past its initial capacity

The Rust Version (Won’t Compile)

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    for item in &v {                         // Immutable borrow of v
        if *item % 2 == 0 {
            v.push(*item * 10);              // ❌ Compile error!
        }
    }                                        //   cannot borrow `v` as mutable
}                                            //   because it is also borrowed as immutable

The Rust compiler catches this because the for loop holds an immutable reference to v, and push requires a mutable reference. Rule 3 says these cannot coexist.

The error message is helpful:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
  --> src/main.rs:5:13
   |
3  |     for item in &v {                    // immutable borrow occurs here
   |                  --
4  |         if *item % 2 == 0 {
5  |             v.push(*item * 10);          // mutable borrow occurs here
   |             ^^^^^^^^^^^^^^^^^^
6  |         }
7  |     }                                    // immutable borrow ends here

The compiler even tells you where the immutable borrow starts and ends β€” no guessing, no debugger attached.


πŸ› Bug 2: Use-After-Free

The C++ Version

int* dangling_ptr() {
    int x = 42;
    return &x;  // Returns pointer to stack-local that will be destroyed
}

int main() {
    int* p = dangling_ptr();
    std::cout << *p << std::endl;  // Maybe prints 42, maybe crashes
    return 0;
}

With Clang/GCC at -O2, this compiles without warning (unless you specifically enable -Wreturn-local-addr on some versions, but not universally). The pointer p dangles the moment dangling_ptr returns.

The Rust Version

fn dangling_ref() -> &i32 {
    let x = 42;
    &x                                       // ❌ Compile error!
}

fn main() {
    let p = dangling_ref();
    println!("{p}");
}
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:23
  |
1 | fn dangling_ref() -> &i32 {
  |                       ^ expected named lifetime parameter
  |
help: consider using the `'static` lifetime
  |
1 | fn dangling_ref() -> &'static i32 {

Even the notation &i32 without a lifetime is caught immediately. Adding 'static won’t help β€” there is no &'static i32 to return from a local.


πŸ› Bug 3: Double-Free

The C++ Version

class Buffer {
    int* data;
public:
    Buffer(size_t n) : data(new int[n]) {}
    ~Buffer() { delete[] data; }
    // No copy constructor or assignment operator!
};

int main() {
    Buffer a(100);
    Buffer b = a;  // Shallow copy! Both a and b point to same memory
    return 0;      // Double free: both destructors call delete[] on same address
}

The Rule of Three (or Five in C++11) is easy to forget, and the compiler does not enforce it. The double-free manifests as a heap corruption crash β€” often in an unrelated malloc/free called hours later.

The Rust Version

struct Buffer {
    data: Vec<i32>,
}

fn main() {
    let a = Buffer { data: vec![0; 100] };
    let b = a;           // Moves ownership from a to b
    // println!("{}", a.data.len());  // ❌ Compile error: a is moved
}                          // Only b's destructor runs β€” clean!

Rust’s move semantics make the β€œforgot the copy constructor” bug impossible: let b = a moves ownership, not shallow copies. You must explicitly opt into cloning:

let b = a.clone();  // βœ… Explicit shallow copy (data is reference-counted)

And Vec’s Clone implementation does a deep copy, so there’s no shared memory to double-free.


πŸ”₯ Real Scenario: Concurrent Cache

Here is a more realistic example that Rust catches at compile time while C++ ships.

The problem: A shared cache with concurrent readers and a periodic writer. The writer holds a lock that invalidates an internal index while a reader is mid-iteration.

C++ (UB ships to production)

#include <unordered_map>
#include <shared_mutex>
#include <thread>
#include <vector>

class ConcurrentCache {
    std::unordered_map<int, std::string> cache;
    mutable std::shared_mutex mtx;

public:
    void insert(int k, std::string v) {
        std::unique_lock lock(mtx);              // Exclusive lock
        cache[k] = std::move(v);
    }

    std::vector<std::string> match_prefix(const std::string& prefix) {
        std::shared_lock lock(mtx);              // Shared lock (reader)
        std::vector<std::string> results;

        // Mutates cache while iterating β€” UB!
        for (const auto& [k, v] : cache) {
            if (v.starts_with(prefix)) {
                results.push_back(v);
                // If we also prune expired entries:
                // cache.erase(k);  ← Iterator invalidation (hidden in real code)
            }
        }
        return results;
    }
};

The erase inside the loop invalidates the iterator β€” UB. In a large codebase, the prune logic may be buried behind a helper function, or added months later by another developer who doesn’t realize the caller is iterating.

Rust (Compile-time guarantee)

use std::collections::HashMap;
use std::sync::{Arc, RwLock};

struct ConcurrentCache {
    cache: RwLock<HashMap<i32, String>>,
}

impl ConcurrentCache {
    fn insert(&self, k: i32, v: String) {
        let mut cache = self.cache.write().unwrap();
        cache.insert(k, v);
    }

    fn match_prefix(&self, prefix: &str) -> Vec<String> {
        let cache = self.cache.read().unwrap();  // Read lock

        // ❌ Won't compile β€” cache is an immutable reference
        // cache.insert(0, "foo".into());

        // βœ… Iterating is safe because cache is immutably borrowed
        cache.iter()
            .filter(|(_, v)| v.starts_with(prefix))
            .map(|(_, v)| v.clone())
            .collect()
    }
}

The RwLock::read() returns a RwLockReadGuard that derefs to &HashMap. Calling insert would require &mut HashMap β€” which the compiler forbids. The mutation can never happen.

And HashMap::iter() borrows the map immutably for as long as the iterator lives, so even without the RwLock, this code won’t compile:

let mut map = HashMap::new();
for (k, v) in &map {    // Immutable borrow begins
    map.insert(0, "x".into());  // ❌ Mutable borrow while immutable exists
}                         // Immutable borrow ends

βš–οΈ The Tradeoff

The borrow checker is Rust’s most controversial feature β€” and its most valuable.

The Pain

Per the Rust Foundation 2025 Survey:

StatisticValue
Developers citing borrow checker as top frustration71%
Developers who later value it most89%
Average time to feel productive in Rust3–6 months

Practical Strategies

Use Rc/Arc for shared ownership when no single owner is natural:

use std::rc::Rc;

struct Node {
    value: i32,
    children: Vec<Rc<Node>>,  // Multiple nodes can share children
}

let leaf = Rc::new(Node { value: 1, children: vec![] });
let branch = Rc::new(Node {
    value: 2,
    children: vec![Rc::clone(&leaf), Rc::clone(&leaf)],  // Reference count: 2
});

Use RefCell for interior mutability when you need runtime borrow checking:

use std::cell::RefCell;

struct Cache {
    inner: RefCell<HashMap<String, Vec<u8>>>,
}

impl Cache {
    fn get_or_compute(&self, key: &str) -> Vec<u8> {
        // Immutable self β†’ fine
        if let Some(val) = self.inner.borrow().get(key) {
            return val.clone();
        }
        // Runtime mutable borrow (panics if already borrowed)
        let computed = compute(key);
        self.inner.borrow_mut().insert(key.to_string(), computed.clone());
        computed
    }
}

Restructure to avoid overlapping borrows β€” the most common fix is separating concerns:

// ❌ Problematic: trying to hold two references from one struct
struct Viewer {
    data: Vec<i32>,
    cursor: usize,
}

impl Viewer {
    fn advance_bad(&mut self) {
        let data = &self.data;          // Immutable borrow
        let val = data[self.cursor];
        self.cursor += 1;               // ❌ Mutable borrow
    }

    // βœ… Solution: split into separate borrows
    fn advance_good(data: &[i32], cursor: &mut usize) {
        let val = data[*cursor];
        *cursor += 1;
    }
}

πŸ“Š Real-World Impact

Bug ClassC++ DetectionRust DetectionFrequency in C++ Codebases
Iterator invalidationRuntime (UB)Compile time15–20% of all CVEs in memory-unsafe code
Use-after-freeRuntime (UB)Compile time~30% of Chrome security bugs (per Google)
Double-freeRuntime (UB)Compile time~10% of Firefox security bugs
Data raceHeuristic tools (TSan)Compile time (Send/Sync)40–70% of concurrency bugs

Google has reported a 70% reduction in security bugs in Android’s Rust code vs equivalent C++ code. Microsoft reports that ~70% of their CVEs are memory safety bugs that Rust would prevent.


πŸš€ Final Thought

The borrow checker is not the compiler being difficult β€” it is the compiler being honest. Every borrow-check error is a bug that would have been a segfault, a security advisory, or a β€œworks on my machine” defect in C++.

The learning curve is real, but the payoff is equally real: Rust code that, once it compiles, is memory-safe and data-race-free by construction.


πŸ“š Further Reading

ResourceLink
The Rust Book β€” Ownershipdoc.rust-lang.org/book/ch04-00-understanding-ownership.html
Rustonomicon β€” Unsafe Rustdoc.rust-lang.org/nomicon/
Rust 2025 Survey Resultsblog.rust-lang.org/2025/02/15/2025-survey-results.html
Google Rust Adoption Reportsecurity.googleblog.com/2024/12/memory-safety-in-android-ecosystem.html
CrUX: C++ vs Rust Safety Statschadaustin.me/2023/08/rust-vs-cpp-safety-stats/

πŸ“– Series Navigation

 praneshnikhar.site / posts / rust-borrow-checker Β· Top 1:1