API Tour: Construction and Conversion


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:

  1. Pointer must point to valid, initialized T
  2. Must be properly aligned
  3. Must have permission (no aliasing violations)
  4. Lifetime 'a must 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

  1. Use each construction method and verify behavior
  2. Test niche optimization with size_of assertions
  3. Implement pointer arithmetic safely with bounds checking
  4. Create NonNull from both &T and &mut T
  5. Write a function that safely converts NonNull to &T

Next: Chapter 4 — Provenance, Aliasing, and UB ⚡