title: “NonNull with Slices and Fat Pointers in Rust” meta_description: “Using NonNull with dynamically-sized types, fat pointers, and pointer metadata in Rust.” keywords: [“rust fat pointers”, “nonnull slice”, “dst rust”, “pointer metadata”]
📦 Reinventing from Scratch: NonNull<T>
Chapter 7 — “Slices, Fat Pointers, and Struct Embedding”
“Some pointers carry extra data — and NonNull handles them elegantly.”
🎯 7.1. Fat Pointers
Some types need more than just an address:
// Thin pointer (just address)
let ptr: *mut i32 = ...; // 8 bytes
// Fat pointer (address + metadata)
let slice: *mut [i32] = ...; // 16 bytes (address + length)
let trait_obj: *mut dyn Debug = ...; // 16 bytes (address + vtable)
NonNull supports fat pointers too!
let nn_slice: NonNull<[i32]> = ...; // 16 bytes
let nn_trait: NonNull<dyn Debug> = ...; // 16 bytes
📏 7.2. NonNull with Slices
use std::ptr::NonNull;
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr;
// Create NonNull<[i32]>
let nn: NonNull<[i32]> = NonNull::from(slice);
unsafe {
let slice_ref: &[i32] = nn.as_ref();
assert_eq!(slice_ref.len(), 5);
assert_eq!(slice_ref[2], 3);
}
Key point: Length is stored in the pointer itself!
🏗️ 7.3. Building Box<[T]>
pub struct BoxSlice<T> {
ptr: NonNull<[T]>, // Fat pointer with length!
}
impl<T> BoxSlice<T> {
pub fn from_vec(vec: Vec<T>) -> Self {
let len = vec.len();
let ptr = vec.as_ptr() as *mut T;
std::mem::forget(vec); // Don't drop!
// Create fat pointer
let slice = unsafe {
std::slice::from_raw_parts_mut(ptr, len)
};
let nn = NonNull::from(slice);
Self { ptr: nn }
}
pub fn as_slice(&self) -> &[T] {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for BoxSlice<T> {
fn drop(&mut self) {
unsafe {
let slice = self.ptr.as_mut();
let ptr = slice.as_mut_ptr();
let len = slice.len();
// Drop each element
std::ptr::drop_in_place(slice);
// Deallocate
let layout = Layout::array::<T>(len).unwrap();
dealloc(ptr as *mut u8, layout);
}
}
}
🎭 7.4. NonNull with Trait Objects
use std::fmt::Debug;
pub struct BoxDyn {
ptr: NonNull<dyn Debug>, // Fat pointer with vtable!
}
impl BoxDyn {
pub fn new<T: Debug + 'static>(value: T) -> Self {
let boxed = Box::new(value);
let ptr: *mut dyn Debug = Box::into_raw(boxed);
let nn = NonNull::new(ptr).unwrap();
Self { ptr: nn }
}
pub fn debug(&self) {
unsafe {
let debug_ref: &dyn Debug = self.ptr.as_ref();
println!("{:?}", debug_ref);
}
}
}
🔍 7.5. Extracting Metadata
use std::ptr;
let slice = &[1, 2, 3, 4, 5][..];
let nn: NonNull<[i32]> = NonNull::from(slice);
// Extract metadata (length)
let ptr = nn.as_ptr();
let metadata = ptr::metadata(ptr); // Returns usize (length)
assert_eq!(metadata, 5);
🧠 7.6. Chapter Takeaways
- Fat pointers = Pointer + metadata
- NonNull<[T]> = Non-null slice pointer
- NonNull
= Non-null trait object - Metadata = Length (for slices) or vtable (for traits)
- Still 2 words = Not more expensive than raw fat pointer
🚀 7.7. What’s Next
Next: Chapter 8 — Send, Sync, Variance, and Covariance
Learn how NonNull affects type system properties!
Next: Chapter 8 — Send, Sync, Variance, and Covariance 🔄