Unsafe Rust

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:

  1. Non-null: ptr must not be 0x0 (!ptr.is_null()).
  2. Properly aligned: (ptr as usize) % align_of::<T>() == 0 (even for zero-sized types).
  3. Allocated and live: ptr must point to size_of::<T>() bytes within a single live allocation (or be any non-null aligned address if size_of::<T>() == 0).
  4. Initialized (on read or reference creation): Reading *ptr or creating &*ptr / &mut *ptr requires that T's bit validity invariant holds at that address.
  5. Aliasing compliance: Creating &*ptr requires no concurrent mutation for the lifetime of the shared reference; creating &mut *ptr requires 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