Jump to content

6 Times Unsafe Rust Was Actually the Right Choice

From JOHNWICK

A single unsafe block once saved our payment service from collapse.
It was the only way to stop latency from spiking and throughput from falling off a cliff.


Unsafe is not evil. It is power wrapped in responsibility.
Used with intent, it can make the impossible both safe and fast. This is a look at six real scenarios where unsafe Rust was the right call — and why.


1. Calling a Battle-Tested C Library Without Copies Problem: Rewriting a C encoder in Rust was slower and error-prone.
Change: Wrapped it safely with one clear unsafe boundary. extern "C" { fn c_encode(p: *const u8, len: usize, out: *mut u8) -> usize; } pub fn encode_fast(input: &[u8], out: &mut [u8]) -> usize {

   unsafe { c_encode(input.as_ptr(), input.len(), out.as_mut_ptr()) }

} Keep the boundary small. Test everything outside it.
Never hide unsafe in helpers that grow silently.


2. Compact Memory for Millions of Small Structs Problem: A metrics collector was wasting cache lines.
Change: Packed structs manually for tighter layout.

  1. [repr(C, packed)]

struct Metric { a: u32, b: u32 } let bytes = unsafe {

   std::slice::from_raw_parts((&Metric { a: 1, b: 2 }) as *const _ as *const u8, 8)

}; Unsafe earns its place when memory alignment makes or breaks scale.


3. Lock-Free Queue for Real-Time Pipelines Problem: Mutexes killed low-latency ingestion.
Change: Built a simple SPSC queue with atomics.

use std::sync::atomic::{AtomicUsize, Ordering}; use std::cell::UnsafeCell; struct Slot<T>(UnsafeCell<Option<T>>); unsafe impl<T: Send> Sync for Slot<T> {} Keep ownership rules tight and reviewed. One producer, one consumer — nothing more.


4. Global Table Loaded Once, Read Forever Problem: Huge lookup tables were rebuilt on every request.
Change: Initialize once, freeze it, and reuse forever. static mut TABLE: Option<Vec<u64>> = None; static ONCE: std::sync::Once = std::sync::Once::new(); fn get_table() -> &'static Vec<u64> {

   unsafe {
       ONCE.call_once(|| TABLE = Some(vec![1, 2, 3]));
       TABLE.as_ref().unwrap()
   }

} Use for read-only data that never mutates.
Document the invariant in one clear sentence.


5. Scoped Bump Allocation for Short-Lived Objects Problem: Frequent tiny allocations hit latency.
Change: Allocate a single region per request and reset. struct Bump { base: *mut u8, used: usize } impl Bump { fn alloc(&mut self, n: usize) -> *mut u8 { let p = unsafe { self.base.add(self.used) }; self.used += n; p } } Free all at once, not individually.
Use only in well-bounded scopes.


6. Zero-Cost Parsing When Protocols Guarantee Safety Problem: Frames already validated upstream.
Change: Trusted layout directly for speed.

  1. [repr(C)]

struct Header { a: u32, b: u64 } fn parse(buf: &[u8]) -> Header {

   unsafe { *(buf.as_ptr() as *const Header) }

} Do this only when protocol versioning, size, and endianness are guaranteed.


Diagrams Unsafe boundaries in controlled layers ┌────────────┐ ┌──────────────┐ │ Safe logic │ ---> │ Unsafe edge │ ---> Native / FFI └────────────┘ └──────────────┘ Memory arena in request scope Request start

  |
  v
[Create bump arena]
  |
  v
[Fast allocs]
  |
  v
[Reset]

These simple ASCII visuals make the reader pause and absorb the mental model.

Closing Thoughts Unsafe is not about rebellion. It is about responsibility.
You take full control, which means full accountability. When used to isolate small, high-impact regions — unsafe Rust is not a risk.
It is a tool of mastery. If you are brave enough to hold that tool carefully, you will write systems that others said were impossible in safe Rust.