- Abstract Models & Hardware β Turing Machines, Von Neumann, and Harvard. Rust's architectural safety.
- ISA & Assembly β The Hardware Contract. Registers, x86 vs. ARM, and SIMD power.
- Virtual Memory β The Great OS Illusion. MMU, TLB, and Stack Overflow protection.
- Memory Physics: Stack, Heap, and Statics β Why Stack beats Heap and how cache locality drives performance.
- Processes vs. Threads β The Cost of Isolation. Context Switching and resource overhead.
- Multitasking β Preemptive vs. Cooperative. OS Threads vs. Async/Await.
- Multi-core & Cache Hierarchy β L1, L2, L3 and the insidious False Sharing problem.
- Memory Synchronization & Data Races β Atomics, Mutexes, and Out-of-Order execution.
- Resource Management (RAII) β Deterministic cleanup. Why Rust doesn't need a Garbage Collector.
We are not just learning history. These three models determine the physical performance limits (why the CPU waits for memory) and security constraints (why viruses are possible) in any modern code.
This is the theoretical limit of computability.
- Essence: An infinite memory tape + a head (read/write) + a rule table.
- Engineering Meaning: This is the benchmark. If a task cannot be solved on a Turing Machine, it cannot be solved on a supercomputer or in Rust.
- Turing Completeness: Any programming language (including Rust's type system!) is "Turing complete" if it can simulate this machine. This means that, at the logical level, Rust is as powerful as C++ or Python.
The de facto standard for RAM in all modern PCs and servers.
-
Principle: A unified bus and unified memory for Code (
.text) and Data (.data,.bss, heap, stack). -
The Von Neumann Bottleneck:
-
The processor runs hundreds of times faster than memory can deliver data.
-
Since there is only one bus, the CPU cannot read an instruction and load data for it simultaneously.
-
Solution: Caches (L1/L2/L3) and Branch Prediction, which attempt to guess what is needed next.
-
Main Vulnerability: Since code and data are just bytes in a single memory, the processor can accidentally (or maliciously) execute data as code.
-
Example: Buffer Overflow attack. An attacker writes more data into an array than allocated, overwriting the function return address with their own malicious code.
Separation of flows for speed and protection.
- Principle: Physically different memory units and buses for instructions and data. Code can be read and variables loaded simultaneously.
- Where it lives today:
- Microcontrollers (AVR, Arduino): Code lives in Flash, data lives in SRAM.
- Inside the CPU (Modified Harvard): Your Intel/AMD processor looks like Von Neumann on the outside (one RAM stick), but inside it features a split L1 cache: L1 Instruction Cache and L1 Data Cache. This bypasses the bus bottleneck.
Modern OSs (Linux, Windows) and processors use a hybrid approach.
1. Emulating Protection (W^X β Write XOR Execute) To patch the Von Neumann hole, modern OSs mark memory pages:
- Either Writable (data can be written here).
- Or eXecutable (this can be run as code).
- Never both! Attempting to execute code from the stack (where data resides) triggers a hardware exception (Segfault).
2. JIT Compilers & Pain Languages with JIT (Java, JS in browsers, Python PyPy) are forced to constantly toggle these flags to generate code on the fly and execute it immediately. This is expensive and complex from a security standpoint.
Rust is a language that knows about these architectural issues:
- The
usizeType: This is a direct bridge to the architecture. Its size equals the data bus width (address width) of your CPU. On a 64-bit machine,usize= 8 bytes because that is exactly what is needed to address the entire Von Neumann memory. - Immutability by Default: Harvard architecture makes code hardware-immutable. Rust makes variables software-immutable. It is an emulation of Harvard's reliability within flexible Von Neumann memory.
- Separation of Code and Data:
- When you compile Rust, functions go into the
.textsection (Read-only, Executable). - Static variables (
static) go into.data. - Attempting to write to a function pointer in Rust code causes a crash because the OS (via MMU) forbids writing to
.text.
Summary for the Engineer: We write code for a Von Neumann machine (shared RAM), but the processor optimizes it like Harvard (split caches), and Rust ensures we don't shoot ourselves in the foot by confusing data with instructions.
The primary "physical" danger of the Von Neumann model compared to the Harvard architecture lies in the method of storing instructions and data.
According to the sources, this difference manifests in the following aspects:
- Unified Memory (Von Neumann): In this model, program instructions and data reside within the same memory block. This means that if a program contains an error (for instance, when working with pointers), it can accidentally overwrite its own executable code, treating it as ordinary data.
- Separate Memory (Harvard): This architecture utilizes separate memory blocks for instructions and data.
- Code Immutability: In Harvard architecture, the memory containing instructions is often immutable, meaning the executing program physically cannot modify it. This creates a natural barrier against many types of attacks and critical errors.
In modern practice, as noted in the sources, the Harvard architecture is often "emulated" at the software level to achieve a similar level of security in systems that are physically built on Von Neumann principles.
ISA (Instruction Set Architecture) is more than just a list of commands. It is a legal contract between the programmer (compiler) and the transistors. This contract defines what the processor must be able to do, regardless of its internal design.
The modern world is split between two philosophies, and a Rust engineer must understand the difference, as it affects optimization and power consumption.
-
x86-64 (CISC β Complex Instruction Set Computer):
-
Philosophy: "One command does a lot." There are instructions that read from memory, add, and write back all at once.
-
Price: Complex decoder in the CPU (consumes more power), but the code is compact.
-
Habitat: Desktops, most servers, gaming consoles.
-
Arm64 / RISC-V (RISC β Reduced Instruction Set Computer):
-
Philosophy: "Simplicity and Speed." Commands are elementary (Load, Add, Store). To perform a complex action, more instructions must be written.
-
Price: Longer code, higher load on the compiler, but the processor is simpler and more energy-efficient.
-
Habitat: Apple Silicon (M1/M2/M3), AWS Graviton, mobile devices, embedded systems.
The most vital mental model for performance.
- Registers: The CPU's "Workbench". There are few of them (16-32 general-purpose). Access is instant (0 cycles). Math (addition, bitwise shifts) happens only here.
- RAM: The "Warehouse". Accessing it is expensive (hundreds of cycles).
- How it works: The CPU cannot add numbers that reside in RAM. It must:
- Load data from the warehouse to the workbench (
LOAD). - Perform the work (
ADD). - Send it back to the warehouse (
STORE).
- Optimization: The Rust compiler's (LLVM) job is to ensure your variables live in registers as long as possible and touch RAM as little as possible.
A 1-to-1 textual representation of machine code.
- Why it's needed: Not to write it (thatβs painful), but to read it. When Rust code runs slowly, we look at the assembly to understand: Did the compiler vectorize the loop? Did it remove unnecessary
bound checks? - Tool: Godbolt (Compiler Explorer) β An engineer's best friend for checking what your beautiful iterator actually turned into.
π§ Deep Dive: Hidden Complexity
1. Pipeline & Superscalar Execution Modern CPUs don't execute one instruction at a time; they execute a batch. They look ahead at the instruction stream (Out-of-Order Execution), find tasks that don't depend on each other, and run them in parallel.
- Impact on code: If there are many branches (
if/else) in your code, the processor mispredicts, flushes the pipeline, and you lose performance. In Rust, iterators are often faster thanforloops because it is easier for the compiler to unroll them into linear code without branching.
2. SIMD (Single Instruction, Multiple Data)
Using massive registers (128, 256, 512 bits) to add 4, 8, or 16 numbers with a single command.
- Rust (via LLVM) can perform Auto-vectorization. If you write clean code without side effects, the compiler will automatically replace standard commands with SIMD.
1. usize is the Native Register Size
The usize (and isize) type isn't just a "number for indices." It is a number that fits perfectly into one general-purpose register and can address all available memory.
- On x86-32,
usize= 4 bytes. - On x86-64,
usize= 8 bytes. Usingu64on a 32-bit machine will force the CPU to use two instructions for a single operation (handling low and high parts separately), which is slower.
2. ABI (Application Binary Interface) By default, Rust does not guarantee a stable layout of fields in memory (structs can be reordered for density).
- Assembly and C require a rigid order.
- Therefore, when interacting with the outside world (OS, C-libs), we write
#[repr(C)]andextern "C". This is a command to the Rust compiler: "Forget your optimizations, lay out the bytes exactly as required by the standard architecture contract."
3. Memory Ordering (C++20 / Rust Memory Model) On x86 processors, there is a Strong Memory Model β writes reach other cores predictably. On ARM (M1/M2), it is Weak.
- Rust provides atomics (
std::sync::atomic) that allow you to control this behavior (Relaxed,Acquire,Release). Understanding the ARM ISA is necessary to write lock-free code that won't break when moving from Intel to Apple Silicon.
Programs in the modern world live in "The Matrix." No process (whether it's a Rust binary or a browser) knows where its data actually resides. They are granted Virtual Memory β an ideal, contiguous, and empty space.
The MMU (Memory Management Unit) is a hardware translator inside the processor.
- Virtual Address: What your pointer sees in Rust (
0x7fff...). - Physical Address: The actual cell in the RAM stick.
- Translation Process: Every time you access a variable, the MMU halts the flow, looks into the Page Table, finds the match, and retrieves the data.
- TLB (Translation Lookaside Buffer): To avoid searching the table (which resides in slow RAM) every single time, the processor caches the most recent translations in the TLB. A TLB Miss is expensive (10β100 cycles).
Memory is divided into blocks of 4 KiB (the standard).
- Isolation: If a process tries to access a page not recorded in its table, the MMU triggers a hardware interrupt. The OS catches it and sends the process a
SIGSEGV(Segmentation Fault) signal. - Fragmentation: You cannot allocate exactly 1 byte of physical memory. The OS will always provide at least one full page (4 KiB).
When a program requests memory, the OS "lies": "Here is your address, go ahead and write." But physical memory is not allocated yet (Lazy Allocation).
- At the moment of the first actual write, a Page Fault occurs.
- The CPU is interrupted -> control passes to the OS kernel -> the kernel finds a free physical page -> updates the tables -> returns control.
- Major Page Fault: If there isn't enough RAM and data has been moved to Swap (on the disk), the process freezes for milliseconds (an eternity for the CPU) while the data travels from the disk.
π§ Deep Dive: Hidden Complexity
1. Context Switch & TLB Flush Multitasking is expensive not just because of register saving. When the CPU switches from one program to another, old TLB entries become invalid (the addresses might be the same, but they point to different data).
- The TLB must be cleared (Flush).
- The new program starts with a "cold" TLB, causing slowdowns at the beginning of its time slice.
- PCID (Process-Context Identifiers) in modern CPUs help avoid flushing the entire cache by tagging entries with a process ID.
2. Huge Pages For databases (PostgreSQL, Redis) and heavy Rust applications (compilers, ML), standard 4 KiB pages are a pain. The page table becomes gigantic, and the TLB overflows.
- Solution: Enable Huge Pages (2 MiB or 1 GiB).
- Fewer entries in the table -> fewer TLB misses -> performance boost of 10β15%.
1. Stack Overflow Protection How does Rust know the stack has overflowed? At the end of the virtual range allocated for the stack, the OS places a special Guard Page with no read/write permissions.
- When recursion hits it, a Page Fault occurs.
- The Rust runtime catches this and panics: "stack overflow," instead of silently corrupting the heap.
2. Zero-Copy Deserialization (rkyv, capnproto)
Virtual memory allows the use of the mmap system call.
- You map a file from the disk directly into the process's virtual memory.
- In Rust, you get a
&[u8]that points directly to the file. - As you read, the OS transparently loads pages from the disk.
- The Benefit: You don't copy bytes from kernel to user-space (
readis unnecessary). This is the foundation of ultra-fast Rust databases.
3. ASLR (Address Space Layout Randomization) Every time a Rust program runs, the addresses of functions and the stack will be different (random offset). This is a security feature against hackers that works thanks to the flexibility of virtual memory.
Memory is not just an abstract storage space. It is a hierarchy of speeds and trade-offs. Understanding where your bytes reside is what distinguishes fast code from lagging code.
This is where data that is known before the program even starts lives.
-
Location: Inside the executable file itself (
.exe/ ELF). When you run the program, the OS simply copies this chunk of the file into RAM. -
Two Sub-types:
-
.data: Initialized global variables (string literals, constants). -
.bss: Variables that are zero by default. They occupy no space on the disk but take up space in RAM. -
Rust Context:
-
staticβ lives forever (for the entire duration of the program). -
static mutβ this is unsafe because accessing it from different threads is a guaranteed Data Race. In modern Rust, we useOnceLockorLazyLockinstead ofstatic mut.
This is the fastest memory available to the programmer.
- Mechanism: It is simply a pre-allocated chunk of memory (usually 2β8 MB per thread).
- Speed: Allocating memory on the stack is a single CPU instruction (shifting the
SPregister β Stack Pointer). Deallocation is just shifting it back. There is no searching for free space. - Cache Locality (The Main Secret): The top of the stack is almost always in the L1 cache of the processor. Working with local variables happens instantly, without accessing the slow RAM.
- The Price:
- Hard size limit (Stack Overflow).
- Data must have a known size at compile time (
Sizedin Rust). You cannot put an array of unknown length here.
Dynamic memory for data whose size is unknown or changes.
- Allocator: This is a program inside your program that manages this memory. When you request memory (
Box::new), the allocator scans the heap to find a free piece of the appropriate size. This is slow. - Fragmentation: Over time, the heap turns into "Swiss cheese"βmany holes that are too small for large objects to fit into.
- Pointer Chasing (Speed Killer): Data in the heap is scattered randomly. To read a
LinkedList, the processor must jump to different addresses, constantly missing the cache (Cache Miss).
1. System Calls (brk/sbrk vs mmap)
The allocator doesn't poke the OS kernel for every malloc. It asks the OS for large chunks (arenas) via mmap and then carves them into small objects for your program itself.
- Why it matters: Changing the allocator can speed up a program significantly. Rust uses the system allocator by default, but for high-load scenarios, Jemalloc or Mimalloc are often plugged in (they handle fragmentation better in multi-threaded environments).
2. Stack Allocation and Inlining The Rust compiler (LLVM) aggressively tries to keep everything on the stack.
- If you create a small struct, pass it to a function, and return it back, the compiler might not use memory at all, passing the data only through CPU registers.
1. The Sized Trait
Everything placed on the stack must implement Sized.
- Why can't you do
let x: str = "hello";? Becausestr(a string slice) has an unknown size. - We are forced to use
&str(a pointer, which isSized) orString(a struct on the heap, which isSized).
**2. Box<T> and Vec<T>**
These are your tickets to the heap.
Vec<T>consists of 3 fields on the stack:pointer(to the heap),capacity(reserved space), andlength(occupied space).- When a vector grows, Rust asks the allocator for a new, larger chunk of memory, copies all the data there, and deletes the old one. This is a heavy operation ().
- Pro Tip: Always use
Vec::with_capacity(n)if you know the approximate size to avoid unnecessary reallocations.
3. Escape Analysis (What Rust lacks) In Go or Java, the compiler decides whether to put a variable on the stack or the heap (Escape Analysis). In Rust, you decide this explicitly.
- No
Boxβ it's on the stack. Box/Rc/Arcβ it's on the heap. This provides full control over the load on the GC (which doesn't exist) and memory.
Textbooks often focus on "isolation." In practice, we talk about Overhead.
A process is a container for resources.
- What's inside: Its own page table (virtual memory), its own file descriptors (open sockets/files), and its own environment variables.
- Creation: System calls like
fork()(in Unix) orCreateProcess(in Win) are expensive. The kernel must copy structures and create a new page table. - Context Switch: When the CPU switches between processes, it is mandatory to flush the TLB (address cache). The next few thousand instructions will run slowly until the cache warms up again.
A thread is a unit of execution within a process.
-
What's inside: Only its own stack, CPU registers, and an instruction pointer. Everything else is shared with the parent process.
-
Shared Memory: Threads see the same heap and global variables.
-
Pro: Communication between threads is as simple as reading a variable (zero overhead).
-
Con: Requires synchronization (mutexes); otherwise, a Data Race occurs.
-
Cost: Creating a thread is cheaper (no need to copy page tables). Switching between threads is also cheaper (the TLB is not flushed because the address space remains the same).
How to divide a single core among thousands of tasks?
The standard for OS threads (std::thread in Rust).
- Mechanism: The CPU timer sends an interrupt every N milliseconds. The OS kernel freezes the current thread, saves all its registers to the kernel stack, and loads the registers of the next thread.
- Pro: If your code hangs in a
while trueloop, the OS will simply seize control. The system doesn't freeze. - Con: You don't control when you are interrupted. It can happen in the middle of updating a complex data structure (hence the need for locks).
The foundation of Async/Await (tokio, async-std in Rust).
-
Mechanism: The code itself says: "I'm waiting for network data, I don't need the CPU, take control" (
.await/yield). -
Scheduler: Instead of the OS kernel, a library (Runtime) in user space acts as the scheduler.
-
Pro:
-
Context switching costs pennies (no need to enter kernel mode or save all registers).
-
You can hold 100,000 asynchronous tasks (green threads) on 2 GB of RAM.
-
Con: If one task starts calculating Fibonacci numbers and doesn't call
await, it will block the entire thread.
π§ Deep Dive: Hidden Complexity
1. CPU Affinity Switching threads is expensive not just because of registers. If a thread jumps from Core 1 to Core 2, it loses its L1/L2 cache. Data must be hauled from RAM all over again.
- High-load trick: We use "CPU Pinning," hard-coding threads to specific cores so the cache stays "hot."
2. M:N Threading How do your tasks map to real cores?
- 1:1 (Rust
std::threadmodel): One language thread = One OS thread. Simple, reliable, but uses a lot of memory for stacks (2 MB each). - M:N (Rust
tokio/ Go goroutines model): Thousands of tasks (M) are multiplexed onto a small number of real OS threads (N). This is extreme efficiency.
1. Send and Sync β Compiler Magic
Rust is the only language that knows about threads at the type system level.
Send: Data can be moved to another thread. (e.g.,Rc<T>is notSend; the compiler won't let you pass it to a thread because the reference counter isn't atomic).Sync: Data can be viewed (&T) from multiple threads simultaneously. (e.g.,Cell<T>is notSync).- Result: You get a compilation error before running the code if you try to do something thread-unsafe. This is "Fearless Concurrency."
2. Why is mutex different in Rust?
In C++/Java, a mutex is a separate object. You can forget to acquire it and still access the data. In Rust, Mutex<T> owns the data.
- You cannot access the data
Twithout locking the mutex (lock().unwrap()). - The language architecture forces you to write correct multi-threaded code.
3. Panic in a Thread If a thread panics in Rust, it dies, but the process lives on (isolation within the process).
- However, if that thread was holding a
Mutex, that mutex becomes Poisoned. Other threads will find out about this when they try to lock it. This is a safeguard against using data that might have been left in a semi-modified state.
A modern processor is a distributed system. Cores don't just calculate; they communicate with each other through a complex memory system. Understanding this physics is key to Highload.
-
Physical Core: An actual piece of silicon containing the ALU (Arithmetic Logic Unit) and FPU (Floating Point Unit).
-
Logical Core (Hyper-Threading / SMT): This is marketing that has become a standard.
-
One physical core pretends to be two logical ones.
-
What's the catch? Resources (L1/L2 caches, execution units) are shared. If two threads on the same core perform heavy math, they slow each other down. SMT is only useful when one thread is waiting for memory/network while the second one is calculating.
-
Rust Context:
std::thread::available_parallelism()will return the number of logical cores. Do not run more heavy computations than there are physical cores.
A processor operates at 3-5 GHz (0.3 ns per clock cycle). DRAM (RAM) responds in 60-100 ns. This is a chasm. To bridge this gap, a cascade of SRAM caches is used.
-
L1 Cache (Instruction + Data): Private to the core.
-
Size: ~32-64 KB.
-
Speed: ~3-4 cycles. (Near-instant).
-
L2 Cache: Usually private, but larger.
-
Size: ~256-1024 KB.
-
Speed: ~10-12 cycles.
-
L3 Cache: Shared across all cores (LLC β Last Level Cache).
-
Size: 10-64 MB and up.
-
Speed: ~40-70 cycles.
-
Problem: Contention occurs here. If one core floods the L3 with its data, others starve.
-
RAM (DRAM): A "distant planet."
-
Speed: ~200-300 cycles.
-
Every trip to RAM is a processor Stall.
A processor never reads just 1 byte. It always reads a Cache Line (usually 64 bytes).
- If you read an
i32(4 bytes), the processor pulls a 64-byte chunk from memory, grabbing its "neighbors." - Spatial Locality:
- Arrays (
Vec<T>) are fast because after loading the first element, you get the next 15 elements in the L1 cache for free. - Linked lists (
LinkedList) are slow because each element resides in a new cache line, forcing a trip to RAM every time.
The most insidious multi-threading problem that compilers stay silent about.
Scenario:
You have AtomicU64 counter_a (for Core 1) and AtomicU64 counter_b (for Core 2). They are stored next to each other in memory.
- Core 1 modifies
counter_a. - Since they hit the same cache line (64 bytes), the coherence protocol (MESI) declares the entire line "dirty" and invalidates it in Core 2's cache.
- Core 2 wants to modify
counter_bbut is forced to wait while the data synchronizes with L3/RAM. - Result: The cores bounce this line back and forth (Ping-pong). Performance drops by 10-50x, even though the data is logically independent.
1. Data Oriented Design (DOD) Rust encourages the use of Struct of Arrays or dense arrays.
- Rust iterators (
.iter().map()) are brilliantly optimized by LLVM into vector instructions that fit perfectly into cache lines.
**2. #[repr(align(N))]**
To avoid False Sharing in high-load systems, we use alignment.
#[repr(align(64))] // Force data into different cache lines
struct AlignedCounter {
value: AtomicUsize,
}In the crossbeam crate (the de facto standard for concurrency), there is a ready-made CachePadded<T> struct that handles this for you.
**3. Vec<Box<T>> vs Vec<T>**
Vec<Box<T>>: A vector of pointers. The data itself is scattered across the heap. Terrible for the cache (Pointer Chasing).Vec<T>: Data sits as a monolithic block. The CPU is happy; the prefetcher loads data into L1 in advance. This is Rust's default behavior and one of the reasons for its speed.
When multiple cores in a system work with the same RAM, a need for strict coordination of their actions arises.
- The Visibility Problem: Without special mechanisms, changes made by one core may remain "unnoticed" by another core.
- Hardware Tools: At the hardware level, atomic instructions, load-link/store-conditional mechanisms, and memory barriers are used for this purpose.
- OS Tools: The operating system provides software tools such as mutexes, futexes, and signals.
- Data Race: This is the primary danger in the absence of synchronization. A program begins to behave unpredictably because the result depends on which core "managed" to access a memory cell first.
- Cost: It is important to consider that synchronization always carries a certain (albeit small) price in terms of system performance.
Addition: For a Rust beginner, this is one of the most pleasant topics: the language is designed such that most synchronization errors (those very data races) are caught at the compilation stage, rather than during program execution.
This is the final leg of our marathon. This topic is the heart of Rust. Without understanding RAII, you will struggle with the language; with it, you will write the most reliable software of your life. We are finally answering the question: "Why doesn't Rust need a Garbage Collector?"
In GC languages (Java, Python, Go), the programmer acts like a child: they scatter toys (objects) around, and a special nanny (the Garbage Collector) follows behind to clean up. Itβs convenient, but the nanny sometimes stops the entire game ("Stop the World") to tidy up.
In Rust (and C++), we live by the principle of RAII (Resource Acquisition Is Initialization).
-
Birth (Acquisition): Allocating a resource (memory, socket, file) is inextricably linked to creating an object on the stack.
-
let file = File::open(...)β the file is now open. -
Death (Release): Resource release is strictly tied to Scope.
-
As soon as a variable reaches the closing brace
}, the destructor is called. -
The "Tracking Structure": In reality, this "remote control" is:
-
For
Box<T>: Just a single pointer (8 bytes). -
For
Vec<T>: A structure of three fields (Pointer, Capacity, Length) β 24 bytes. -
It resides on the stack, while the actual data is in the heap.
The main difference from GC is that we know exactly when memory will be freed.
-
This allows using RAII not just for memory, but for any resource:
-
Closing database connections.
-
Releasing a mutex.
-
Committing a transaction.
-
In GC languages, you are forced to write
finally { ... }ordefer. In Rust, it happens automatically.
1. Drop Order A common interview question: In what order are variables destroyed?
- Variables inside a function: In reverse order of creation (LIFO, like a stack).
- Fields inside a struct: In order of declaration.
- Why it matters: If you have a
socketand acontext, and the socket requires the context to close properly, the order of fields in the struct is critically important.
2. Memory Leaks in Rust Does RAII guarantee deallocation? No. Rust guarantees memory safety (no segfaults), but leaks are considered "safe."
- Circular References: If you create an
Rc(Reference Counted) that points to itself, the reference count will never hit zero. Memory will leak. (Fixed viaWeak). std::mem::forget: You can explicitly tell the compiler: "Forget this variable, do not call the destructor." This is used for FFI (passing control to C), but if misused, it causes a leak.
1. Move Semantics This makes RAII in Rust unique. In C++, assignment triggers a copy. In Rust, it moves ownership.
let a = Box::new(5);let b = a;- Now
bowns the memory;ais invalidated. - Result: The destructor is called exactly once (for
b). The "Double Free" problem that haunted C developers for 40 years is solved mathematically.
2. MutexGuard β RAII for Locks
In Rust, you can't just call mutex.unlock(). You do let guard = mutex.lock().unwrap().
- While
guardis alive (on the stack), the mutex is locked. - Once
guardgoes out of scope (Drop), the mutex unlocks itself. - You physically cannot forget to unlock the thread.
**3. ManuallyDrop**
Sometimes we need to cheat RAIIβfor example, when writing custom data structures or working with Union. We wrap the value in ManuallyDrop<T>, and the compiler relinquishes responsibility. Calling drop then becomes your manual task (via unsafe).