Preface: Why NonNull<T> After Box<T>?

Reinventing From Scratch — NonNull<T>

Chapter 0 — Preface: Why NonNull<T> After Box<T>?

NonNull<T> is a tiny wrapper around a raw pointer that promises one thing:

This pointer is never null.

That single promise unlocks niche optimizations (Option<NonNull<T>> has the size of a single pointer), clarifies invariants inside data structures, and reduces foot-guns compared to *mut T / *const T.

We’ll write book‑style chapters that turn NonNull<T> from a “mysterious standard type” into a tool you can confidently use to implement containers (Vec, slabs, arenas, intrusive lists) and smart pointers.


Deep Dive: Provenance, Niche Layouts, and Safe Projections

A. Invariants for NonNull<T>

  • N1 (Never-Null): The wrapped pointer cannot be null from construction to drop.
  • N2 (No Deref Promise): NonNull<T> alone does not promise that a valid T is there—just that the pointer is non-null.
  • N3 (Provenance Respect): Converting to &T/&mut T is only legal if you hold the corresponding borrow capability; as_ref/as_mut encode that at the type level.
  • N4 (Layout Discipline): Casting NonNull<U> to NonNull<T> requires T and U be layout-compatible; misalignment is UB on use.

B. Option Niche Demonstration

On most platforms:

use std::ptr::NonNull;
assert_eq!(std::mem::size_of::<Option<NonNull<u8>>>(),
           std::mem::size_of::<*mut u8>>());

The null pointer pattern acts as the None tag “for free,” yielding compact lists and intrusive structures.

C. Fat Pointers and Metadata

  • NonNull<[T]> is a fat pointer; length travels with the pointer and must be consistent with your actual allocation.
  • NonNull<dyn Trait> pairs data with a vtable; you must not fabricate vtables—leave trait object creation to the compiler.

D. Safe Projections with Late Borrowing

Prefer to keep data as raw pointers during shuffles (memmoves, swaps), and produce &T/&mut T only at the last moment. This reduces the chance of aliasing violations and keeps the borrow window short.

E. Exercises

  1. Show that a zero-capacity vector may store NonNull::dangling() and still be safe so long as no elements are read/written.
  2. Build a singly-linked list node with Option<NonNull<Node>> and write push_front, pop_front adjusting only raw pointers, turning them into refs only when reading/writing value.