Patterns, Internals, and FFI


title: “NonNull Patterns and FFI Usage” meta_description: “Common patterns using NonNull in intrusive data structures, arenas, and FFI with C libraries.” keywords: [“nonnull ffi”, “intrusive list rust”, “arena allocator”, “nonnull patterns”]

🌐 Reinventing from Scratch: NonNull<T>

Chapter 9 — “Patterns, Internals, and FFI”

“NonNull shines in intrusive data structures and C interop.”


🔗 9.1. Pattern: Intrusive Linked List

struct Node<T> {
    value: T,
    next: Option<NonNull<Node<T>>>,
    prev: Option<NonNull<Node<T>>>,
}

pub struct IntrusiveList<T> {
    head: Option<NonNull<Node<T>>>,
    tail: Option<NonNull<Node<T>>>,
    len: usize,
}

impl<T> IntrusiveList<T> {
    pub fn push_back(&mut self, value: T) {
        let node = Box::new(Node {
            value,
            next: None,
            prev: self.tail,
        });
        
        let ptr = NonNull::from(Box::leak(node));
        
        match self.tail {
            Some(mut tail) => unsafe {
                tail.as_mut().next = Some(ptr);
            },
            None => self.head = Some(ptr),
        }
        
        self.tail = Some(ptr);
        self.len += 1;
    }
}

Benefits: No separate allocation for links — embedded in nodes!


🏗️ 9.2. Pattern: Arena Allocator

pub struct Arena {
    chunks: Vec<NonNull<u8>>,
    current: NonNull<u8>,
    remaining: usize,
}

impl Arena {
    pub fn alloc<T>(&mut self, value: T) -> NonNull<T> {
        let layout = Layout::new::<T>();
        
        if layout.size() > self.remaining {
            self.grow(layout.size());
        }
        
        unsafe {
            let ptr = self.current.cast::<T>();
            ptr.as_ptr().write(value);
            
            self.current = NonNull::new_unchecked(
                self.current.as_ptr().add(layout.size())
            );
            self.remaining -= layout.size();
            
            ptr
        }
    }
}

🌉 9.3. FFI: C Interop

Passing to C

#[repr(C)]
struct CData {
    value: i32,
    next: Option<NonNull<CData>>,  // C sees this as pointer
}

extern "C" {
    fn process_list(head: *mut CData);
}

fn pass_to_c(head: NonNull<CData>) {
    unsafe {
        process_list(head.as_ptr());
    }
}

Receiving from C

extern "C" {
    fn get_data() -> *mut CData;
}

fn receive_from_c() -> Option<NonNull<CData>> {
    unsafe {
        let ptr = get_data();
        NonNull::new(ptr)  // Safe: handles null from C
    }
}

🧠 9.4. Chapter Takeaways

NonNull is ideal for:

  • ✅ Intrusive data structures
  • ✅ Arena allocators
  • ✅ FFI with C
  • ✅ Manual memory management

Next: Chapter 10 — Tests, Miri, and Safety Checklist 🧪