Unsafe Rust

Unsafe Abstractions

Unsafe abstractions are reusable data structures, smart pointers, and concurrency primitives that internally use raw pointers, PhantomData, NonNull<T>, and manual memory management to provide capabilities beyond safe built-in references while enforcing lifetime, variance, and drop-check rules through the type system.

Four Pillars of Building a Sound Unsafe Abstraction

When designing a custom smart pointer or collection around raw pointers (*mut T or NonNull<T>), four type-system properties must be configured explicitly because raw pointers do not carry them automatically:

Pillar Problem with Raw *mut T / NonNull<T> Type-System Solution
1. Lifetimes Raw pointers have no lifetime 'a, allowing borrowed references to outlive their owner Add PhantomData<&'a T> (shared) or PhantomData<&'a mut T> (exclusive)
2. Variance *mut T is invariant over T; NonNull<T> is covariant over T even if the container mutates T Add PhantomData<T> or PhantomData<Cell<T>> if invariance is required
3. Send & Sync Raw pointers implement neither Send nor Sync Provide audited unsafe impl<T: Send> Send and unsafe impl<T: Sync> Sync
4. Ownership & Drop Check NonNull<T> does not tell the compiler that dropping the container drops T Add PhantomData<T> so the borrow checker knows T is owned and dropped

Building a Custom Owned Heap Box (MiniBox<T>)

Below is a complete, sound implementation of a single-owner heap smart pointer MiniBox<T> that handles zero-sized types (size_of::<T>() == 0), allocation failures (handle_alloc_error), Deref/DerefMut, Drop, and thread safety (Send/Sync):

use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::{self, NonNull};

pub struct MiniBox<T> {
    ptr: NonNull<T>,
    // Tells the compiler that `MiniBox<T>` logically owns a value of type `T`:
    _marker: PhantomData<T>,
}

// SAFETY: `MiniBox<T>` uniquely owns its heap-allocated `T`, so transferring
// or sharing `MiniBox<T>` across threads has the exact same safety as `T`.
unsafe impl<T: Send> Send for MiniBox<T> {}
unsafe impl<T: Sync> Sync for MiniBox<T> {}

impl<T> MiniBox<T> {
    pub fn new(value: T) -> Self {
        let ptr = if size_of::<T>() == 0 {
            NonNull::dangling()
        } else {
            let layout = Layout::new::<T>();
            // SAFETY: `size_of::<T>() > 0` was checked above.
            let raw = unsafe { alloc(layout).cast::<T>() };
            let Some(non_null) = NonNull::new(raw) else {
                handle_alloc_error(layout);
            };
            // SAFETY: `non_null` points to freshly allocated aligned memory for `T`.
            unsafe {
                non_null.as_ptr().write(value);
            }
            non_null
        };

        Self {
            ptr,
            _marker: PhantomData,
        }
    }
}

impl<T> Deref for MiniBox<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        // SAFETY: `self.ptr` is always aligned, initialized, and valid while `self` lives.
        unsafe { self.ptr.as_ref() }
    }
}

impl<T> DerefMut for MiniBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        // SAFETY: `&mut self` guarantees exclusive access to the owned `T`.
        unsafe { self.ptr.as_mut() }
    }
}

impl<T> Drop for MiniBox<T> {
    fn drop(&mut self) {
        // SAFETY: Drop the inner `T` in place first; then deallocate if non-ZST.
        unsafe {
            ptr::drop_in_place(self.ptr.as_ptr());
            if size_of::<T>() != 0 {
                dealloc(self.ptr.as_ptr().cast::<u8>(), Layout::new::<T>());
            }
        }
    }
}

fn main() {
    let mut boxed = MiniBox::new(String::from("custom heap box"));
    boxed.push_str(" works!");
    let zst_box = MiniBox::new(());

    println!("boxed:   {}", *boxed);
    println!("zst_box: {:?}", *zst_box);
}
boxed:   custom heap box works!
zst_box: ()