25.xChapter 25 summary and quiz

Last updated June 13, 2026

The course's most dangerous chapter, taught as a contract rather than an off-switch. Review, then a quiz.

Quick review

unsafe doesn't disable Rust's safety checks; it grants exactly five extra abilities the compiler can't verify (25.1): dereference a raw pointer, call an unsafe function, access a mutable static, implement an unsafe trait, access a union field. Inside an unsafe block the borrow checker and everything else stay fully active. Every unsafe block gets a // SAFETY: comment justifying why it's sound. It exists because the borrow checker is conservative and because talking to the OS/hardware/C requires it, the standard library is built on contained unsafe.

Raw pointers *const T and *mut T are addresses without a reference's guarantees (25.2): they can be null, dangling, or aliased, with no lifetime. Creating them is safe (just computing an address); dereferencing them needs unsafe (that's where invalid memory bites).

Unsafe functions carry caller preconditions and need unsafe to call (25.3). The core pattern is wrapping a tiny unsafe core in a safe abstraction that guarantees the preconditions for all inputs, split_at_mut returns two disjoint mutable slices (sound, but unprovable by the checker) behind a safe API. Mark a function safe only if it's truly safe for every input.

FFI crosses the language boundary. Calling C from Rust uses extern "C" declarations, and every foreign call is unsafe because Rust can't verify the foreign code (25.4); data crosses as C types (CString, raw pointers). Calling Rust from C uses #[no_mangle] pub extern "C" plus a cdylib build (25.5), making Rust callable from any C-speaking language, "Rust in anything", with defensive handling of untrusted foreign inputs.

Quiz time

Question #1

A teammate says "wrap it in unsafe to get the borrow checker to stop complaining." Why is that wrong?

Show solution

Because unsafe does not turn off the borrow checker, ownership, lifetimes, or type checking, all of those stay fully active inside an unsafe block. unsafe only adds five specific abilities (dereferencing raw pointers, calling unsafe functions, etc.). A normal borrow error won't go away by wrapping it in unsafe; you'd still get the same error. unsafe is for operations the compiler can't verify, not a way to silence checks it can.

Question #2

Why is creating a raw pointer safe but dereferencing it unsafe?

Show solution

Creating a raw pointer just computes an address (a number), which reads/writes nothing and can't cause harm, so it's allowed in safe code. Dereferencing actually accesses the memory at that address; if the pointer is null, dangling, or invalid, that's undefined behavior the compiler can't rule out. So the harmless act (making the address) stays safe and the dangerous act (trusting it enough to touch memory) requires unsafe.

Question #3

split_at_mut returns two mutable references into one slice, which sounds like it breaks chapter 9's rules. Why is it actually safe, and why does it need unsafe internally anyway?

Show solution

It's safe because the two slices cover disjoint ranges of the original ([0, mid) and [mid, len)), so they never point at the same memory, no aliasing, no real violation. It needs unsafe internally because the borrow checker can't see that the two ranges are disjoint; it only sees two mutable borrows of one slice and refuses. So the implementation uses raw pointers (which ignore the aliasing rules) inside unsafe, guarded by an assert!(mid <= len), and wraps it in a safe function the caller uses with no unsafe.

Question #4

Why does calling a C function from Rust always require unsafe, and why is a Rust function exposed to C written in ordinary safe Rust yet still needing defensive coding?

Show solution

Calling C requires unsafe because Rust can't verify the foreign code, that it matches the declared signature, respects ownership, or stays in bounds, so you vouch for it crossing out of the checked zone. A Rust function exposed to C has a normal safe body (exposing it doesn't make its insides unsafe), but its callers are C, which Rust can't constrain: C may pass wrong types or invalid pointers. So the function must validate inputs and treat foreign data as untrusted, the danger is at the boundary, not in the Rust implementation.

You've now seen the one place Rust's safety guarantee has a seam, and how the language contains it so that "unsafe" means a small, audited, well-justified contract rather than a free-for-all. That completes the language. Chapter 26 is the finale: a complete, working, multithreaded web server that puts the whole course together, ownership, traits, error handling, closures, threads, channels, and smart pointers, in one real program, built step by step and compiling at every stage. And, fittingly, it needs no unsafe at all.