Loading page…
Rust walkthroughs
Loading page…
NonNull is a wrapper around a raw pointer *mut T that guarantees the pointer is never null. It's a building block for safe abstractions over raw pointers, commonly used when implementing custom smart pointers, collections, and FFI bindings.
Key characteristics:
NonNull<&'a T> can be used where NonNull<&'static T> is expected*mut T equivalent — Same size and layout as a raw pointerunsafe or validationWhen to use NonNull:
use std::ptr::NonNull;
fn main() {
let mut value = 42;
// Create NonNull from a reference (safe because reference is non-null)
let ptr: NonNull<i32> = NonNull::from(&mut value);
// Access the value
unsafe {
println!("Value: {}", *ptr.as_ref());
}
// Modify the value
unsafe {
*ptr.as_mut() = 100;
}
println!("Modified: {}", value);
}use std::ptr::NonNull;
fn main() {
let mut value = 42i32;
// From reference (safe)
let ptr1: NonNull<i32> = NonNull::from(&value);
println!("From ref: {:?}", ptr1);
// From raw pointer (unsafe - must verify non-null)
let raw_ptr: *mut i32 = &mut value;
let ptr2: NonNull<i32> = unsafe { NonNull::new_unchecked(raw_ptr) };
println!("From raw: {:?}", ptr2);
// From raw pointer (safe - returns Option)
let ptr3: Option<NonNull<i32>> = NonNull::new(raw_ptr);
println!("From raw (checked): {:?}", ptr3);
// From dangling (for ZSTs or sentinel values)
let dangling: NonNull<i32> = NonNull::dangling();
println!("Dangling: {:?}", dangling);
}use std::ptr::NonNull;
fn main() {
// Option<NonNull<T>> has the same size as *const T
// Because None is represented as a null pointer
let mut value = 42;
let ptr: Option<NonNull<i32>> = Some(NonNull::from(&mut value));
let none: Option<NonNull<i32>> = None;
println!("Size of Option<NonNull<i32>>: {}", std::mem::size_of::<Option<NonNull<i32>>>());
println!("Size of *const i32: {}", std::mem::size_of::<*const i32>());
// Both are 8 bytes on 64-bit!
// Can pattern match safely
match ptr {
Some(p) => println!("Got pointer: {:?}", p),
None => println!("No pointer"),
}
}use std::ptr::NonNull;
use std::ops::Deref;
struct MyBox<T> {
ptr: NonNull<T>,
}
impl<T> MyBox<T> {
fn new(value: T) -> Self {
// Box::into_raw returns *mut T, which is non-null after allocation
let ptr = NonNull::new(Box::into_raw(Box::new(value)))
.expect("Box::new should never return null");
Self { ptr }
}
fn into_inner(self) -> T {
let ptr = self.ptr;
std::mem::forget(self); // Don't run Drop
unsafe { *Box::from_raw(ptr.as_ptr()) }
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.ptr.as_ptr()));
}
}
}
fn main() {
let boxed = MyBox::new(String::from("Hello, World!"));
println!("Value: {}", *boxed);
let inner = boxed.into_inner();
println!("Extracted: {}", inner);
}use std::ptr::NonNull;
// NonNull is covariant in T
// This means NonNull<&'a T> can be used where NonNull<&'static T> is expected
fn main() {
fn accept_static(_: NonNull<&'static str>) {}
let mut value = "hello";
let ptr: NonNull<&str> = NonNull::from(&mut value);
// This works because NonNull is covariant
// (In practice, this example is simplified - lifetimes are checked at compile time)
println!("Covariance demonstrated");
}use std::ptr::NonNull;
struct Node<T> {
value: T,
next: Option<NonNull<Node<T>>>,
}
struct LinkedList<T> {
head: Option<NonNull<Node<T>>>,
len: usize,
}
impl<T> LinkedList<T> {
fn new() -> Self {
Self { head: None, len: 0 }
}
fn push(&mut self, value: T) {
let node = Box::into_raw(Box::new(Node {
value,
next: self.head,
}));
self.head = NonNull::new(node);
self.len += 1;
}
fn pop(&mut self) -> Option<T> {
self.head.map(|node| {
let node = unsafe { Box::from_raw(node.as_ptr()) };
self.head = node.next;
self.len -= 1;
node.value
})
}
fn len(&self) -> usize {
self.len
}
fn iter(&self) -> Iter<T> {
Iter {
next: self.head.map(|n| n.as_ptr()),
}
}
}
struct Iter<'a, T> {
next: *const Node<T>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.next.is_null() {
None
} else {
unsafe {
let node = &*self.next;
self.next = node.next.map(|n| n.as_ptr()).unwrap_or(std::ptr::null());
Some(&node.value)
}
}
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}
fn main() {
let mut list = LinkedList::new();
list.push(1);
list.push(2);
list.push(3);
println!("Length: {}", list.len());
for val in list.iter() {
println!("Value: {}", val);
}
while let Some(val) = list.pop() {
println!("Popped: {}", val);
}
}use std::ptr::NonNull;
use std::ffi::c_void;
// Many C APIs guarantee non-null pointers
// NonNull helps encode this in the type system
// C function: void process_data(void* data) - data must not be null
fn process_data_ffi(data: NonNull<c_void>) {
// Safe: C function expects non-null pointer
unsafe {
// simulate_c_process_data(data.as_ptr());
println!("Processing data at {:?}", data);
}
}
fn main() {
let mut data = vec![1u8, 2, 3, 4];
let ptr = NonNull::new(data.as_mut_ptr() as *mut c_void).unwrap();
process_data_ffi(ptr);
}use std::ptr::NonNull;
fn main() {
let mut value = 42i32;
let ptr: NonNull<i32> = NonNull::from(&mut value);
// as_ptr: Get *mut T
let raw: *mut i32 = ptr.as_ptr();
println!("Raw pointer: {:?}", raw);
// as_ref: Get &T (unsafe)
let reference: &i32 = unsafe { ptr.as_ref() };
println!("Reference: {}", reference);
// as_mut: Get &mut T (unsafe)
let mut_ref: &mut i32 = unsafe { ptr.as_mut() };
*mut_ref = 100;
println!("Modified: {}", value);
// cast: Convert NonNull<T> to NonNull<U>
let bytes: NonNull<u8> = ptr.cast();
println!("As bytes: {:?}", bytes);
}use std::ptr::NonNull;
fn main() {
let mut arr = [1, 2, 3, 4, 5];
// Get NonNull to array
let ptr: NonNull<[i32; 5]> = NonNull::from(&mut arr);
// Get NonNull to first element
let first: NonNull<i32> = unsafe {
NonNull::new_unchecked(arr.as_mut_ptr())
};
// Iterate through array using NonNull
let mut current = first;
for i in 0..5 {
unsafe {
println!("Element {}: {}", i, *current.as_ref());
current = NonNull::new_unchecked(current.as_ptr().add(1));
}
}
}use std::ptr::NonNull;
struct MaybePresent<T> {
ptr: Option<NonNull<T>>,
}
impl<T> MaybePresent<T> {
fn new() -> Self {
Self { ptr: None }
}
fn set(&mut self, value: T) {
let ptr = NonNull::new(Box::into_raw(Box::new(value)))
.expect("Allocation should not fail");
self.ptr = Some(ptr);
}
fn get(&self) -> Option<&T> {
self.ptr.map(|p| unsafe { p.as_ref() })
}
fn take(&mut self) -> Option<T> {
self.ptr.take().map(|p| unsafe {
*Box::from_raw(p.as_ptr())
})
}
}
impl<T> Drop for MaybePresent<T> {
fn drop(&mut self) {
if let Some(ptr) = self.ptr {
unsafe {
drop(Box::from_raw(ptr.as_ptr()));
}
}
}
}
fn main() {
let mut maybe = MaybePresent::new();
println!("Has value: {}", maybe.get().is_some());
maybe.set(String::from("Hello"));
println!("Has value: {}", maybe.get().is_some());
println!("Value: {}", maybe.get().unwrap());
let value = maybe.take();
println!("Took: {:?}", value);
}use std::ptr::NonNull;
// ZSTs have no data, but we still need a "pointer" for them
struct Empty;
fn main() {
// dangling() provides a non-null but invalid pointer
// Safe because ZSTs don't actually dereference memory
let zst_ptr: NonNull<Empty> = NonNull::dangling();
println!("ZST pointer: {:?}", zst_ptr);
// Size of pointer to ZST is still just a pointer
println!("Size of NonNull<Empty>: {}", std::mem::size_of::<NonNull<Empty>>());
}use std::ptr::NonNull;
// NonNull<T> is !Send and !Sync (like raw pointers)
// But if T is Send/Sync, you can implement it manually
struct ThreadSafeBox<T> {
ptr: NonNull<T>,
}
// SAFETY: We guarantee exclusive access to the data
// In real code, you'd need proper synchronization!
unsafe impl<T: Send> Send for ThreadSafeBox<T> {}
unsafe impl<T: Sync> Sync for ThreadSafeBox<T> {}
fn main() {
fn check_send<T: Send>() {}
fn check_sync<T: Sync>() {}
check_send::<ThreadSafeBox<i32>>();
check_sync::<ThreadSafeBox<i32>>();
println!("ThreadSafeBox is Send + Sync");
}use std::ptr::NonNull;
fn main() {
// Pattern 1: Store allocated pointer
struct Owned<T> {
ptr: NonNull<T>,
}
// Pattern 2: Optional pointer without Option overhead
struct MaybeOwned<T> {
ptr: Option<NonNull<T>>, // Same size as *const T
}
// Pattern 3: Self-referential structure
struct Node {
value: i32,
prev: Option<NonNull<Node>>,
next: Option<NonNull<Node>>,
}
// Pattern 4: FFI non-null guarantee
fn ffi_wrapper(ptr: *mut i32) -> Option<i32> {
NonNull::new(ptr).map(|p| unsafe { *p.as_ref() })
}
println!("Patterns demonstrated");
}NonNull Methods:
| Method | Description | Safety |
|--------|-------------|--------|
| new(ptr) | Create from *mut T (returns Option) | Safe |
| new_unchecked(ptr) | Create from *mut T (assumes non-null) | Unsafe |
| dangling() | Create dangling pointer (for ZSTs) | Safe |
| from(ref) | Create from reference | Safe |
| as_ptr() | Get *mut T | Safe |
| as_ref() | Get &T | Unsafe |
| as_mut() | Get &mut T | Unsafe |
| cast<U>() | Convert to NonNull<U> | Safe |
Comparison with Related Types:
| Type | Null? | Size | Send/Sync |
|------|-------|------|-----------|
| *mut T | Can be null | Pointer size | !Send, !Sync |
| NonNull<T> | Never null | Pointer size | !Send, !Sync |
| &T | Never null | Pointer size | Send if T: Sync, Sync if T: Sync |
| &mut T | Never null | Pointer size | Send if T: Send, !Sync |
| Box<T> | Never null | Pointer size | Send if T: Send, Sync if T: Sync |
| Option<NonNull<T>> | None is null | Pointer size | !Send, !Sync |
When to Use NonNull:
| Scenario | Appropriate? |
|----------|-------------|
| Custom smart pointers | ✅ Yes |
| Collections (Vec, LinkedList) | ✅ Yes |
| FFI non-null pointers | ✅ Yes |
| Option pointer optimization | ✅ Yes |
| Self-referential structures | ✅ Yes |
| Normal code | ❌ Use references |
| Nullable FFI pointers | ❌ Use *mut T directly |
Key Points:
NonNull<T> guarantees the pointer is never nullOption<NonNull<T>> has the same size as a raw pointerT (unlike *mut T which is invariant)dangling() for ZSTs or sentinel valuesMaybeUninit for uninitialized pointer wrappers