Rust Raw Pointers: When References Aren't Enough
Raw pointers (*const T, *mut T) are Rust's C-style pointers, bypassing the borrow checker. They're used for FFI or building low-level abstractions. The footgun is assuming they're safe; they can be null or dangling, requiring `unsafe` to dereference.
WHY IT EXISTS: Rust's borrow checker is powerful but conservative. It sometimes rejects valid code because it cannot prove its safety. Raw pointers provide an escape hatch for these situations, allowing programmers to implement patterns the compiler doesn't understand, especially when interfacing with other languages or performing low-level system optimizations.
THE MENTAL MODEL: Think of raw pointers as a contract with the compiler. By creating one, you are saying, "I will manually uphold Rust's memory safety rules for this pointer." The compiler allows you to create them in safe code, but to dereference them and access the underlying data, you must wrap the operation in an unsafe block, explicitly acknowledging you are taking responsibility for safety.
HOW IT WORKS: Raw pointers exist as const T (immutable) and *mut T (mutable). You can create them by casting references, like let ptr = &my_var as *const i32;. Unlike Rust's safe references (&T), raw pointers are not bound by the borrow checker's rules. They can be null, they can point to invalid memory (be "dangling"), and you can have multiple mutable pointers to the same location. Creating them is always safe; using them (dereferencing with `) is not, and must happen inside an unsafe` block.
WHEN TO USE IT: The two primary use cases are calling code in other languages (Foreign Function Interface, or FFI), especially C libraries that heavily use pointers, and building safe, high-level abstractions whose internal workings are too complex for the borrow checker to verify. For example, the implementation of Vec<T> uses raw pointers internally to manage its growable buffer, but it exposes a completely safe public API.
WHEN NOT TO USE IT: Avoid raw pointers in regular application code whenever possible. If your problem can be solved with safe Rust constructs like references, Box<T>, Rc<T>, or Arc<T>, you should always use them. Reaching for unsafe and raw pointers negates many of Rust's core safety guarantees and should be a last resort, not a convenience.
ONE CANONICAL EXAMPLE: Creating both an immutable and a mutable pointer to the same data is forbidden with references but allowed with raw pointers. Consider let mut num = 5;. You can create let r1 = &num as *const i32; and let r2 = &mut num as *mut i32;. To use them, you enter an unsafe block: unsafe { println!("Value via r1: {}", *r1); *r2 = 10; }. Here, you are responsible for ensuring that reading from r1 while r2 could be writing does not cause a data race.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.