blog.dopana

Back

Second post in Part 3: Advanced Systems & Production Rust. While Rust is celebrated for its ironclad compile-time safety guarantees, real-world systems programming — operating system kernels, hardware drivers, interfacing with legacy C/C++ codebases, or squeezing every drop of CPU performance — requires operations that the static compiler simply cannot verify.

Rust accommodates these realities with the unsafe keyword.

graph TD
    A["Unsafe Rust"] --> B["5 Unsafe Superpowers"]
    B --> B1["Dereference Raw Pointers (*const T, *mut T)"]
    B --> B2["Call unsafe functions or foreign FFI functions"]
    B --> B3["Implement an unsafe trait"]
    B --> B4["Access or modify mutable static variables"]
    B --> B5["Access fields of unions"]
    A --> C["C Foreign Function Interface (FFI)"]
    A --> D["Pattern: Safe Abstractions over Unsafe Core"]

Explain Like I’m 10: The Welder’s Safety Shield#

  • Safe Rust: Like the light switch in your bedroom. You flip it on and off with 100% safety because all high-voltage wiring is insulated and pre-certified.
  • Unsafe Rust: Like opening the breaker box directly. The compiler steps back and says: “I cannot statically check live wires here. I am giving you full manual control and total responsibility!”
  • unsafe does not turn off the borrow checker or make Rust untyped; it precisely grants access to five specific low-level capabilities.

1. Raw Pointers (*const T and *mut T)#

Unlike references (&T and &mut T), raw pointers:

  • Ignore borrowing rules (you can have multiple mutable pointers to the same address).
  • Are not guaranteed to point to valid memory (they can be null or dangling).
  • Do not implement automatic cleanups (no automatic Drop).

[!NOTE] Creating raw pointers is completely safe. Only dereferencing (*pointer) to read or write memory requires an unsafe block.

src/main.rs
fn main() {
    let mut num = 42;

    // Creating raw pointers from references (Safe)
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // Dereferencing raw pointers (Unsafe)
    unsafe {
        println!("r1 points to value: {}", *r1);
        *r2 = 100;
        println!("r2 modified value to: {}", *r2);
    }
}
rust

2. Calling Unsafe Functions and Safe Abstractions#

The golden architectural pattern in Rust is: Keep unsafe code isolated in small internal modules, and wrap it with a safe public API.

A classic example from the standard library is split_at_mut, which safely splits a single mutable slice into two non-overlapping mutable slices:

3. Foreign Function Interface (FFI): Talking to C#

Rust can invoke C functions with zero runtime overhead using the standard C ABI:

Calling C Standard Library Functions#

src/main.rs
use std::ffi::c_int;

// Declare external C ABI function
extern "C" {
    fn abs(input: c_int) -> c_int;
}

fn main() {
    unsafe {
        let result = abs(-42);
        println!("Absolute value from C standard library: {result}");
    }
}
rust

Exporting Rust Functions to C/C++#

You can compile Rust code into static or dynamic shared libraries (.so, .dylib, .dll) callable by C, Python, Go, or Node.js:

#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}
rust
  • #[no_mangle]: Instructs the Rust compiler not to mangle the symbol name, making it discoverable by the C linker.
  • extern "C": Instructs the compiler to use the C calling convention.

Best Practices for Unsafe Code#

  1. Minimize Unsafe Scopes: Keep unsafe blocks as small and localized as possible.
  2. Document Invariants: Add comments explaining why the unsafe block is sound.
  3. Use Miri for Validation: Run cargo miri test to detect Undefined Behavior (UB), memory leaks, and out-of-bounds pointer operations during test execution.

Summary#

  • unsafe unlocks 5 low-level capabilities necessary for hardware access, OS development, and FFI.
  • Raw pointers (*const T, *mut T) provide unconstrained pointer manipulation when needed.
  • FFI enables seamless two-way interoperability with existing C/C++ libraries.
  • Always encapsulate unsafe primitives inside sound, safe abstractions.

References#