25.2Raw pointers
The first of unsafe's five powers (lesson 25.1) is dereferencing a raw pointer, and the other powers build on it, so it's where the chapter gets concrete. A raw pointer is the lowest-level pointer Rust has: just a memory address, with none of the guarantees that references carry. You've used references (&T, &mut T) since chapter 9, and the borrow checker has kept them airtight. Raw pointers are references with the safety rails removed, which is exactly why creating one is fine but following one needs unsafe.
Two raw pointer types
Raw pointers come in two kinds, mirroring references:
*const T is a raw pointer to a T you intend to read (like &T).
*mut T is a raw pointer to a T you intend to modify (like &mut T).
(Read *const T as a single type name, "raw const pointer to T"; the * here is part of the type, not the dereference operator.) The difference from references isn't the syntax, it's everything the compiler stops promising. A raw pointer:
- is allowed to be null (point at nothing), which a reference never is;
- is allowed to dangle (point at freed or invalid memory), which the borrow checker forbids for references;
- ignores the borrowing rules entirely, you can have many
*mut Tto the same data at once, with no aliasing checks; - has no lifetime, the compiler tracks nothing about how long it stays valid.
Every one of those is a guarantee references give you and raw pointers throw away. That's the trade: raw pointers can express things references can't (a nullable pointer for C interop, multiple mutable views the borrow checker would reject), at the cost of the compiler no longer protecting you.
Creating raw pointers is safe
Here's the surprising part: you can create raw pointers in completely ordinary, safe code. No unsafe needed. You make them with the as cast or the &raw operators, most commonly by casting a reference:
fn main() {
let mut num = 5;
let r1 = &num as *const i32; // a *const i32 (read-only raw pointer)
let r2 = &mut num as *mut i32; // a *mut i32 (mutable raw pointer)
println!("created two raw pointers");
}created two raw pointers
No unsafe anywhere, and this compiles fine. Why is making a raw pointer safe? Because a pointer is just a number, an address, and computing an address can't, by itself, cause any harm. Nothing has been read or written. Note also that r1 and r2 point at the same num, a *const and a *mut to one value coexisting, which the borrow checker would never allow for &num and &mut num. Raw pointers don't play by those rules, so even creating both is allowed.
Dereferencing requires unsafe
The line is crossed the moment you use a raw pointer to actually touch memory. Following a raw pointer, reading or writing through it with *, is one of the five superpowers, and it requires an unsafe block:
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 points at: {}", *r1); // read through raw pointer
*r2 = 10; // write through raw pointer
println!("r1 now points at: {}", *r1);
}
}r1 points at: 5
r1 now points at: 10
The *r1 read and the *r2 = 10 write must be inside unsafe, because this is where things could go wrong. If r1 were null, or pointed at freed memory, dereferencing it would be undefined behavior, a crash or worse, and the compiler can no longer prove it won't. By writing unsafe, you're vouching that the pointer really is valid to dereference here (and the // SAFETY: comment from last lesson would record why). The split is deliberate and clean: making the address is harmless and stays safe; trusting the address enough to read or write through it is the dangerous act, and that's what unsafe gates.
The compiler stops helping at the dereference
With references, the borrow checker guarantees that every *r touches valid, correctly-borrowed memory. With raw pointers, it guarantees nothing at the dereference: a raw pointer can be null, dangling, or aliased in ways that would be instant undefined behavior to follow, and the compiler won't warn you. Dereferencing an invalid raw pointer is exactly the use-after-free / null-deref class of bug that safe Rust eliminated, now back on the table because you asked for it. That's why this single operation needs unsafe and a written justification: you've taken over the job the borrow checker was doing.
Why use them at all?
Given all that danger, why do raw pointers exist? Two honest reasons, and both come up later in this chapter. First, interfacing with C (lessons 25.4 and 25.5): C has no concept of Rust's references, it speaks in raw addresses that can be null, so any data crossing the language boundary travels as raw pointers. Second, building safe abstractions the borrow checker can't see the safety of (next lesson): a few data structures need raw pointers internally to express patterns like two views into different halves of one array, then wrap them so callers stay in safe Rust. Outside those cases, you should essentially never reach for a raw pointer; a reference is what you want. Raw pointers are a tool for the boundary and the library author, not for everyday code.
Quiz time
Question #1
Name two guarantees a reference (&T) gives that a raw pointer (*const T) does not.
Show solution
Any two of: a reference is never null (a raw pointer can be); a reference never dangles/points at invalid memory (a raw pointer can); references obey the borrowing rules with aliasing checks (raw pointers ignore them, you can have many *mut to the same data); a reference has a tracked lifetime (a raw pointer has none). Raw pointers drop all of these in exchange for flexibility, which is why the compiler can't protect you when you use one.
Question #2
Why is creating a raw pointer safe, but dereferencing it requires unsafe?
Show solution
Creating a raw pointer just computes an address (a number); nothing is read or written, so it can't cause harm, hence it's allowed in safe code. Dereferencing it actually reads or writes the memory at that address, and if the pointer is null, dangling, or invalid, that's undefined behavior the compiler can't rule out. So the dangerous act (trusting the address enough to touch memory) is gated behind unsafe, while the harmless act (making the address) stays safe.
Question #3
Give one legitimate reason to use raw pointers, given how dangerous they are.
Show solution
Either: interfacing with C/other languages (lessons 25.4–25.5), which speak in raw addresses (possibly null) and have no notion of Rust references, so boundary data travels as raw pointers; or building a safe abstraction whose internal implementation needs a pattern the borrow checker can't verify (e.g. two mutable views into disjoint parts of one array), then wrapping the raw-pointer code in a safe API. Outside the language boundary and library-internals, you should use references, not raw pointers.
A raw pointer is dangerous on its own, but the Rust way is to confine that danger and hand callers something safe. The next lesson (25.3) shows how: unsafe functions, and the crucial pattern of wrapping unsafe code in a safe abstraction, with split_at_mut as the canonical example.