Slices, Fat Pointers, and Struct Embedding


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 🔄