QUICK START:HTMLCSSJavaScriptTypeScriptPythonSQLReactNext.jsNode.jsLinux & UbuntuKotlinSwiftC# / .NETJavaGoRustC++DSASystem DesignDevOpsCybersecurityAI / ML
Advanced 28 min readModule: Module 12: Unsafe Rust: Raw Pointers, Undefined Behavior & Miri

Unsafe Rust: Raw Pointers, Invariants & Miri Validation

Step outside the borrow checker safely with Unsafe Rust: raw pointer arithmetic (`*const T`, `*mut T`), preventing Undefined Behavior (UB), building safe abstraction boundaries (`NonNull<T>`, `ManuallyDrop`), and formal UB verification using Miri.

What You Will Learn in This Lesson

  • The 5 superpowers of `unsafe`: dereferencing raw pointers, calling unsafe functions, implementing unsafe traits, mutating statics, and accessing union fields
  • What constitutes Undefined Behavior (UB) in Rust (aliasing violations, unaligned reads, invalid enum discriminants)
  • The Stacked Borrows and Tree Borrows memory models in Miri
  • Wrapping raw pointer allocations safely in custom smart pointers

Introduction & Core Concept

Rust's primary value proposition is compile-time memory safety. However, at the lowest levels of systems programming (device drivers, lock-free ring buffers, custom heap allocators), the borrow checker cannot prove correctness statically. Unsafe Rust does not disable the compiler; it gives the systems engineer explicit responsibility to uphold Rust's memory invariants without compiler assistance.
WHY DOES THIS MATTER IN THE REAL WORLD?

A single line of unsound Unsafe Rust can corrupt heap memory, cause security vulnerabilities, and crash production systems. Miri (Rust's undefined behavior interpreter) provides mathematical verification of unsafe code.

Syntax & Structure

rust
let ptr: *mut i32 = &mut x as *mut i32;
unsafe {
*ptr += 10;
}
// Run verification: cargo miri test

Building a Safe Custom Dynamic Array with Unsafe Raw Pointers

rust
rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Building a Safe Minimal Vector with Unsafe Raw Pointers
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::ptr::{self, NonNull};
pub struct RawVec<T> {
ptr: NonNull<T>,
cap: usize,
}
impl<T> RawVec<T> {
pub fn new() -> Self {
assert!(std::mem::size_of::<T>() != 0, "Zero-Sized Types not handled in demo");
Self {
ptr: NonNull::dangling(),
cap: 0,
}
}
pub fn grow(&mut self) {
let (new_cap, new_layout) = if self.cap == 0 {
(1, Layout::array::<T>(1).unwrap())
} else {
let new_cap = self.cap * 2;
(new_cap, Layout::array::<T>(new_cap).unwrap())
};
let new_ptr = if self.cap == 0 {
unsafe { alloc(new_layout) }
} else {
let old_layout = Layout::array::<T>(self.cap).unwrap();
unsafe { realloc(self.ptr.as_ptr() as *mut u8, old_layout, new_layout.size()) }
};
// NonNull::new aborts if allocation returned null (OOM)
self.ptr = match NonNull::new(new_ptr as *mut T) {
Some(p) => p,
None => std::alloc::handle_alloc_error(new_layout),
};
self.cap = new_cap;
}
}
impl<T> Drop for RawVec<T> {
fn drop(&mut self) {
if self.cap != 0 {
let layout = Layout::array::<T>(self.cap).unwrap();
unsafe {
dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
println!("✅ Deallocated raw memory block safely via custom Drop.");
}
}
}
fn main() {
println!("=== Unsafe Rust: Raw Memory Vector Allocation ===");
let mut vec: RawVec<u64> = RawVec::new();
vec.grow();
vec.grow();
println!("Allocated RawVec capacity: {} elements", vec.cap);
// Verified free of Undefined Behavior with 'cargo miri test'
}

Line-by-Line Technical Breakdown

1Stacked Borrows in Miri: Rust requires that while an exclusive `&mut T` reference exists, no other references may read or write to that memory. Miri tracks pointer provenance as a stack of borrow permissions; any violation immediately triggers an 'Undefined Behavior' trace.

Try It Yourself (Interactive Editor)

Modify the code in real-time and click Run to test live browser output and console logs.

Intelligent Code Runner & Live Sandbox[RUST]
RUST SOURCE EDITOR
Interactive Live Code

Common Mistakes & How to Avoid Them

#1: Creating multiple `&mut` references to the same memory address simultaneously inside unsafe code.

Having two active `&mut` references to the same memory location is immediate UB in Rust, even if they are never used.

Incorrect / Antipattern
let r1 = &mut *ptr; let r2 = &mut *ptr; // Instant Undefined Behavior (Aliasing violation)
Correct / Professional Solution
let p1 = ptr; let p2 = ptr; // Keep as raw pointers (*mut T) until dereference

Industry Best Practices & Professional Standards

  • Always run test suites with `cargo miri test` to catch memory model violations.
  • Keep `unsafe` blocks as small as possible and document safety invariants with `// SAFETY:` comments.
  • Use `std::ptr::copy_nonoverlapping` (equivalent to `memcpy`) for fast zero-copy memory transfers.

Lesson Summary & Core Takeaways

  • Unsafe Rust grants low-level hardware access while requiring developers to uphold invariants.
  • `NonNull<T>` and `Layout` provide structured raw pointer management.
  • Miri verifies pointer provenance, aliasing rules, and memory alignment.