title: “NonNull API Tour - Construction and Conversion Methods”
meta_description: “Complete guide to NonNull methods: new, dangling, from, as_ptr, as_ref, cast, and more.”
keywords: [“rust nonnull api”, “nonnull methods”, “nonnull construction”, “nonnull conversion”]
🔧 Reinventing from Scratch: NonNull<T>
Chapter 3 — “API Tour: Construction and Conversion”
“A small API, but every method has a purpose.”
🏗️ 3.1. Construction Methods
NonNull::new() — Safe Construction
pub fn new(ptr: *mut T) -> Option<NonNull<T>>
Usage:
let x = 42;
let ptr = &x as *const i32 as *mut i32;
match NonNull::new(ptr) {
Some(nn) => println!("Valid non-null pointer!"),
None => println!("Pointer was null!"),
}
When to use: When you’re not sure if pointer is null.
NonNull::new_unchecked() — Unsafe Construction
pub unsafe fn new_unchecked(ptr: *mut T) -> NonNull<T>
Usage:
unsafe {
let layout = Layout::new::<i32>();
let ptr = alloc(layout) as *mut i32;
// SAFETY: alloc returns non-null (or panics)
let nn = NonNull::new_unchecked(ptr);
}
When to use: When you know pointer is non-null and want to skip the check.
⚠️ Warning: If pointer IS null, this is UB!
NonNull::dangling() — Zero-Capacity Placeholder
pub fn dangling() -> NonNull<T>
Usage:
struct MyVec<T> {
ptr: NonNull<T>,
len: usize,
cap: usize,
}
impl<T> MyVec<T> {
pub fn new() -> Self {
Self {
ptr: NonNull::dangling(), // Non-null but don't dereference!
len: 0,
cap: 0,
}
}
}
Purpose: Placeholder for zero-capacity collections.
⚠️ Never dereference a dangling pointer!
NonNull::from() — From References
impl<T> From<&mut T> for NonNull<T>
impl<T> From<&T> for NonNull<T>
Usage:
let x = 42;
let ptr = NonNull::from(&x); // Always succeeds!
let mut y = 99;
let ptr_mut = NonNull::from(&mut y); // Also works
Why this is safe: References are never null by construction!
🔄 3.2. Conversion Methods
as_ptr() — Back to Raw Pointer
pub fn as_ptr(self) -> *mut T
Usage:
let nn: NonNull<i32> = ...;
let raw: *mut i32 = nn.as_ptr();
unsafe {
*raw = 42; // Use like any raw pointer
}
as_ref() — Borrow as Reference
pub unsafe fn as_ref<'a>(&self) -> &'a T
Usage:
let nn: NonNull<i32> = ...;
unsafe {
// SAFETY: Pointer is valid, aligned, and we have permission
let reference: &i32 = nn.as_ref();
println!("{}", reference);
}
Safety requirements:
- Pointer must point to valid, initialized
T - Must be properly aligned
- Must have permission (no aliasing violations)
- Lifetime
'amust not outlive the allocation
as_mut() — Mutable Borrow
pub unsafe fn as_mut<'a>(&mut self) -> &'a mut T
Usage:
let mut nn: NonNull<i32> = ...;
unsafe {
// SAFETY: Pointer is valid, aligned, and exclusively owned
let reference: &mut i32 = nn.as_mut();
*reference = 100;
}
Extra safety requirement: No other references to this memory exist!
🔢 3.3. Pointer Arithmetic
add() / sub() — Offset by Elements
pub unsafe fn add(self, count: usize) -> NonNull<T>
pub unsafe fn sub(self, count: usize) -> NonNull<T>
Usage:
let arr = [1, 2, 3, 4, 5];
let ptr = NonNull::from(&arr[0]);
unsafe {
let ptr2 = ptr.add(2); // Points to arr[2]
assert_eq!(*ptr2.as_ptr(), 3);
}
Safety: Must stay within bounds of the allocation!
offset() — Raw Offset
pub unsafe fn offset(self, count: isize) -> NonNull<T>
Usage:
let ptr: NonNull<i32> = ...;
unsafe {
let next = ptr.offset(1); // Move forward
let prev = ptr.offset(-1); // Move backward
}
🎨 3.4. Casting Methods
cast<U>() — Change Type
pub fn cast<U>(self) -> NonNull<U>
Usage:
let ptr: NonNull<i32> = ...;
let ptr_bytes: NonNull<u8> = ptr.cast(); // Treat as bytes
unsafe {
let byte = *ptr_bytes.as_ptr();
}
⚠️ Warning: You’re responsible for ensuring the cast is valid!
🧮 3.5. Comparison and Hashing
NonNull implements useful traits:
let ptr1: NonNull<i32> = ...;
let ptr2: NonNull<i32> = ...;
// Equality
if ptr1 == ptr2 {
println!("Same address!");
}
// Ordering
use std::cmp::Ordering;
match ptr1.cmp(&ptr2) {
Ordering::Less => println!("ptr1 < ptr2"),
Ordering::Equal => println!("ptr1 == ptr2"),
Ordering::Greater => println!("ptr1 > ptr2"),
}
// Hashing
use std::collections::HashMap;
let mut map: HashMap<NonNull<i32>, String> = HashMap::new();
map.insert(ptr1, "metadata".into());
📋 3.6. Complete API Reference
| Method | Safety | Purpose |
|---|---|---|
new(ptr) |
Safe | Returns Option<NonNull<T>> |
new_unchecked(ptr) |
Unsafe | Returns NonNull<T>, UB if null |
dangling() |
Safe | Placeholder pointer (don’t deref!) |
from(ref) |
Safe | From &T or &mut T |
as_ptr() |
Safe | Get raw *mut T |
as_ref() |
Unsafe | Borrow as &T |
as_mut() |
Unsafe | Borrow as &mut T |
add(n) |
Unsafe | Pointer arithmetic |
sub(n) |
Unsafe | Pointer arithmetic |
offset(n) |
Unsafe | Raw offset |
cast<U>() |
Safe | Type cast |
🧠 3.7. Chapter Takeaways
NonNull<T> is a thin wrapper with a big impact:
- Communicates “this is non-null” in type system
- Enables
Option<NonNull<T>>optimization - Makes APIs clearer and safer
- Still requires unsafe to dereference
Remember: NonNull is not a magic bullet — it’s just a better raw pointer!
🚀 3.8. What’s Next
We know the API. Now let’s tackle the tricky stuff:
Next: Chapter 4 — Provenance, Aliasing, and UB
Learn about:
- What is provenance?
- How aliasing can cause UB
- Stacked Borrows model
- When casting loses provenance
💡 Exercises
- Use each construction method and verify behavior
- Test niche optimization with
size_ofassertions - Implement pointer arithmetic safely with bounds checking
- Create NonNull from both
&Tand&mut T - Write a function that safely converts NonNull to &T
Next: Chapter 4 — Provenance, Aliasing, and UB ⚡