Dereferencing Raw Pointers
Dereferencing a raw pointer (*ptr) reads, writes, or creates a place expression at the memory address stored in *const T or *mut T. Because the compiler does not track whether a raw pointer is null, unaligned, dangling, or aliased, dereferencing a raw pointer requires an unsafe block (E0133).
Preconditions Required Before Dereferencing *ptr
Whenever you dereference *ptr or convert a raw pointer to a reference (&*ptr or &mut *ptr), you must guarantee five conditions:
- Non-null:
ptrmust not be0x0(!ptr.is_null()). - Properly aligned:
(ptr as usize) % align_of::<T>() == 0(even for zero-sized types). - Allocated and live:
ptrmust point tosize_of::<T>()bytes within a single live allocation (or be any non-null aligned address ifsize_of::<T>() == 0). - Initialized (on read or reference creation): Reading
*ptror creating&*ptr/&mut *ptrrequires thatT's bit validity invariant holds at that address. - Aliasing compliance: Creating
&*ptrrequires no concurrent mutation for the lifetime of the shared reference; creating&mut *ptrrequires exclusive access with no other live references or active raw-pointer accesses.
Specialized Raw Pointer Read and Write Methods
Using *dst_ptr = val on an uninitialized heap slot is a classic bug: the assignment operator *dst_ptr = val runs Drop::drop on the old value at *dst_ptr before writing val! To write to uninitialized memory, or to access unaligned or hardware-mapped memory safely, use the dedicated methods on *const T and *mut T:
| Method | Drops Old Value at Destination? | Alignment Required? | Primary Use Case |
|---|---|---|---|
*ptr = val |
Yes (Drop runs on old *ptr!) |
Yes (align_of::<T>()) |
Overwriting an already-initialized, aligned T |
ptr.write(val) (std::ptr::write) |
No (overwrites raw bytes without dropping) | Yes | Initializing uninitialized heap memory (such as inside Vec::push) |
ptr.read() (std::ptr::read) |
Moves/copies T out bitwise (caller must avoid double-drop) |
Yes | Moving an element out of raw storage (such as inside Vec::pop) |
ptr.write_unaligned(val) / ptr.read_unaligned() |
No | No (1-byte aligned) |
Reading or writing packed structs or unaligned network buffers |
ptr.write_volatile(val) / ptr.read_volatile() |
No | Yes | Memory-mapped I/O (MMIO) hardware registers that must not be elided or reordered |
ptr.as_ref() / ptr.as_mut() |
No | Yes | Safely converts nullable *const T / *mut T into Option<&T> / Option<&mut T> |
use std::alloc::{Layout, alloc, dealloc};
use std::ptr;
fn main() {
let layout = Layout::new::<String>();
// SAFETY: `String` has non-zero size (`24` bytes).
let raw_slot: *mut String = unsafe { alloc(layout).cast::<String>() };
assert!(!raw_slot.is_null(), "allocation failed");
unsafe {
// WRONG: `*raw_slot = String::from("hello")` would try to drop the uninitialized
// garbage bytes in `*raw_slot` as a `String` before writing!
// CORRECT: `raw_slot.write(...)` initializes the slot without dropping old bytes:
raw_slot.write(String::from("initialized via ptr::write"));
// Now that `*raw_slot` is initialized, we can borrow or mutate it normally:
(*raw_slot).push_str(" + mutated");
println!("value at raw_slot: {:?}", *raw_slot);
// Drop the `String` in place before freeing the raw memory block:
ptr::drop_in_place(raw_slot);
dealloc(raw_slot.cast::<u8>(), layout);
}
}
value at raw_slot: "initialized via ptr::write + mutated"
Safe Null-Checked Reference Conversion with .as_ref() and .as_mut()
When interfacing with C pointers that might be NULL, calling unsafe { ptr.as_ref() } returns None if ptr.is_null() and Some(&*ptr) if non-null (provided the non-null pointer is aligned, initialized, and valid):
use std::ptr;
fn inspect_optional_ptr(ptr: *const i32) {
// SAFETY: Caller guarantees that if `ptr` is non-null, it points to a valid `i32`.
match unsafe { ptr.as_ref() } {
Some(&val) => println!("pointer holds: {}", val),
None => println!("pointer is null"),
}
}
fn main() {
let number = 42;
inspect_optional_ptr(&raw const number);
inspect_optional_ptr(ptr::null());
}
pointer holds: 42
pointer is null