2.7 KiB
2.7 KiB
id, original_id, level, impact
| id | original_id | level | impact |
|---|---|---|---|
| ptr-02 | P.UNS.PTR.02 | P | MEDIUM |
Prefer NonNull Over *mut T
Summary
Use NonNull<T> instead of *mut T when the pointer should never be null. This enables null pointer optimization and makes the intent clear.
Rationale
NonNull<T>guarantees non-null at the type level- Enables niche optimization:
Option<NonNull<T>>is the same size as*mut T - Makes invariants explicit in the type system
- Covariant over
T(like&T), which is usually what you want
Bad Example
// DON'T: Use *mut when pointer is always non-null
struct MyBox<T> {
ptr: *mut T, // Invariant: never null, but not enforced
}
impl<T> MyBox<T> {
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
// ptr is guaranteed non-null, but type doesn't show it
Self { ptr }
}
pub fn get(&self) -> &T {
// Must add null check or document the invariant
unsafe { &*self.ptr }
}
}
Good Example
use std::ptr::NonNull;
// DO: Use NonNull when pointer is never null
struct MyBox<T> {
ptr: NonNull<T>, // Type guarantees non-null
}
impl<T> MyBox<T> {
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
// SAFETY: Box::into_raw never returns null
let ptr = unsafe { NonNull::new_unchecked(ptr) };
Self { ptr }
}
pub fn get(&self) -> &T {
// SAFETY: NonNull guarantees ptr is valid
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
// SAFETY: ptr was created from Box::into_raw
unsafe { drop(Box::from_raw(self.ptr.as_ptr())); }
}
}
// DO: Niche optimization with Option
struct OptionalBox<T> {
ptr: Option<NonNull<T>>, // Same size as *mut T!
}
NonNull API
use std::ptr::NonNull;
// Creating NonNull
let ptr: NonNull<i32> = NonNull::new(raw_ptr).expect("null pointer");
let ptr: NonNull<i32> = unsafe { NonNull::new_unchecked(raw_ptr) };
let ptr: NonNull<i32> = NonNull::dangling(); // For ZSTs or uninitialized
// Using NonNull
let raw: *mut i32 = ptr.as_ptr();
let reference: &i32 = unsafe { ptr.as_ref() };
let mut_ref: &mut i32 = unsafe { ptr.as_mut() };
// Casting
let ptr: NonNull<u8> = ptr.cast::<u8>();
When to Use *mut T Instead
- When null is a valid/expected value
- FFI with C code that may return null
- When variance matters (NonNull is covariant, sometimes you need invariance)
Checklist
- Is my pointer ever null? If no, use NonNull
- Do I need null pointer optimization?
- Is the variance correct for my use case?
Related Rules
ptr-03: Use PhantomData for variance and ownershipsafety-06: Don't expose raw pointers in public APIs