Creating Raw Pointers
Creating a raw pointer is completely safe in Rust and does not require an unsafe block—only dereferencing a raw pointer requires unsafe. Rust provides dedicated syntax and standard library constructors for creating raw pointers from references, places, slices, and null/dangling sentinels.
All Ways to Create Raw Pointers in Safe Rust
| Method | Syntax | Result Type | Creates Intermediate Reference? |
|---|---|---|---|
| Raw borrow operators (Rust 1.82+ / 2024) | &raw const place / &raw mut place |
*const T / *mut T |
No (safe on unaligned packed fields and aliased places) |
| Reference coercion / cast | &val as *const T / &mut val as *mut T |
*const T / *mut T |
Yes (must satisfy reference alignment and aliasing rules) |
std::ptr::from_ref / from_mut |
ptr::from_ref(&val) / ptr::from_mut(&mut val) |
*const T / *mut T |
Yes |
| Slice / collection pointers | slice.as_ptr() / slice.as_mut_ptr() |
*const T / *mut T |
Uses existing &[T] or &mut [T] |
| Null pointers | ptr::null::<T>() / ptr::null_mut::<T>() |
*const T / *mut T |
No (0x0 address) |
| Dangling aligned pointer (ZSTs) | ptr::dangling::<T>() / ptr::dangling_mut::<T>() |
*const T / *mut T |
No (align_of::<T>() as non-null address) |
Raw Borrow Operators: &raw const and &raw mut
Creating a reference &place or &mut place requires that place is properly aligned, initialized, and not incompatibly aliased—even if the reference is immediately cast to a raw pointer (&place as *const T).
The raw borrow operators &raw const expr and &raw mut expr (which supersede the older std::ptr::addr_of! and std::ptr::addr_of_mut! macros) compute the raw address of a place directly without creating an intermediate reference. This is essential when taking the address of an unaligned field inside a #[repr(packed)] struct or an uninitialized struct field:
#[repr(C, packed)]
struct WireFrame {
flag: u8,
sequence_id: u32,
}
fn main() {
let mut frame = WireFrame {
flag: 0x01,
sequence_id: 100,
};
// `&frame.sequence_id` would fail to compile (E0793: unaligned reference),
// but `&raw const` and `&raw mut` create raw pointers directly without a reference:
let seq_const_ptr: *const u32 = &raw const frame.sequence_id;
let seq_mut_ptr: *mut u32 = &raw mut frame.sequence_id;
// SAFETY: `seq_mut_ptr` points to a valid (though unaligned) `u32` inside `frame`.
unsafe {
let current = seq_const_ptr.read_unaligned();
seq_mut_ptr.write_unaligned(current + 50);
println!("updated sequence_id: {}", seq_const_ptr.read_unaligned());
}
}
updated sequence_id: 150
Pointer Provenance and Integer-to-Pointer Casts
In Rust's memory model, a pointer carries not just a numeric virtual address (usize), but also provenance—invisible metadata tracking which allocation (stack variable, Box, Vec) the pointer is authorized to access.
When tagging pointers or performing alignment math on addresses, use the Strict Provenance APIs (ptr.addr(), ptr.with_addr(new_addr), ptr.map_addr(f)) rather than casting ptr as usize as *mut T, so the compiler preserves the original pointer's provenance:
fn main() {
let value: u64 = 0xCAFE_BABE;
let ptr: *const u64 = &raw const value;
// Inspect the numeric address without exposing provenance:
let numeric_addr: usize = ptr.addr();
// Attach a 1-bit tag in the unused low alignment bit while preserving provenance:
let tagged_ptr: *const u64 = ptr.map_addr(|a| a | 1);
// Strip the tag bit before dereferencing:
let untagged_ptr: *const u64 = tagged_ptr.map_addr(|a| a & !1);
println!("original addr: {:#x}", numeric_addr);
println!("tag bit: {}", tagged_ptr.addr() & 1);
// SAFETY: `untagged_ptr` has the exact address and provenance of `value`.
println!("dereferenced: {:#x}", unsafe { *untagged_ptr });
}
original addr: 0x16fdfee70
tag bit: 1
dereferenced: 0xcafebabe