We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 2 of 24
The five superpowers — and the four myths
pub fn sum_via_raw(v: Vec<i64>) -> i64
pub fn sum_safe(v: &[i64]) -> i64
Write the same function twice. sum_via_raw must sum the vector by taking a
raw pointer to its buffer and walking it with .add(i). sum_safe must sum
it with no unsafe at all. Both are graded, on the same inputs, and they
must agree. Use wrapping addition in both so the 10 000-element case cannot
overflow into a difference.
The starter does not compile. The error is E0133, and it is the first thing you need to see.
What unsafe actually unlocks
Rust is two languages sharing one syntax. In Safe Rust the compiler proves
memory safety for you. In Unsafe Rust you prove it, and the unsafe keyword
is where you sign your name.
The Book gives five superpowers, and they are worth memorising because the
list is short and closed — there is nothing else unsafe lets you do:
-
dereference a raw pointer (
*const T/*mut T); -
call an
unsafe fn; -
access or modify a mutable
static; -
implement an
unsafe trait; -
access the fields of a
union.
The Rust Reference is the authority, and it lists three more that the Book omits:
-
call a safe
#[target_feature]function from a function that does not have those features enabled; -
declare an
externblock (this became an unsafe operation in edition 2024 —unsafe extern "C" { ... }); -
apply an
unsafeattribute to an item (#[unsafe(no_mangle)],#[unsafe(export_name = "...")]).
Learn the Reference’s eight; keep the Book’s five as the mnemonic. Everything in this track is an instance of one of them.
The four myths
This is the most mis-stated fact about Rust, and getting it wrong is how people write unsafe code that “works” for years and then miscompiles.
unsafe does not turn off the borrow checker. It runs identically inside
an unsafe block. Try it:
let mut x = 5;
unsafe {
let a = &mut x;
let b = &mut x; // E0499 — cannot borrow `x` as mutable more than once
*a += 1;
*b += 1;
}
That is a hard error, inside unsafe, exactly as it is outside. If your real
problem is a borrow error, unsafe is not the tool.
unsafe does not turn off type checking. let x: u32 = "hello"; is still
E0308.
unsafe does not turn off lifetime checking. Returning a reference to a
local is still E0106/E0515.
unsafe does not make anything you write legal. It does not grant
permission; it transfers the obligation. The compiler stops asking you to
prove memory safety and starts assuming you have. If you have not, the program
has no defined meaning at all.
::: question A learner has a function that will not compile because of E0499.
They wrap the offending lines in unsafe { }. What happens?
Nothing changes. The program still fails to compile with E0499.
unsafe widens the set of operations you are allowed to name — the eight
above. It does not weaken any static analysis. The borrow checker, the type
checker and the lifetime checker all run over unsafe blocks with exactly the
rules they use everywhere else.
This is the single most useful thing to know about unsafe, because it tells
you when not to reach for it. E0499, E0502, E0505, E0515, E0597 — none of
those are jobs for unsafe. They are jobs for restructuring, for
split_at_mut, for RefCell, or for changing the signature.
:::
Why this exercise ships two functions
sum_safe is the point of the problem. You will write it in one line, with a
fold or .sum(), and it will compile to the same machine code as the raw
version — probably better, because the iterator carries the length in its type
and LLVM knows it cannot alias.
So the exercise you thought was about pointers is really about calibration:
unsafe was never needed here, and reaching for it would have bought you a
proof obligation in exchange for nothing.
Keep that ratio in mind for the rest of the track. Real unsafe code exists
to do things safe Rust genuinely cannot express — build a Vec out of raw
memory, talk to C, implement a lock — and every one of those has a cost you
must be getting something for.
The gate teaches minimal scope
Two lints are on for this problem, and they are not decoration.
clippy::undocumented_unsafe_blocks is enabled by the template. Every
unsafe { } you write needs a // SAFETY: comment on the immediately
preceding line — no blank line in between, or the lint does not see it.
Item 17.4 is about why that discipline is the difference between auditable
and unauditable code; here it is just muscle memory.
rustc’s unused_unsafe is warn-by-default, and because the grade is
clippy -D warnings, a warning is a failure. An unsafe block containing no
unsafe operation is a lie about your code, and the compiler says so. Wrap the
smallest expression that actually needs it.
::: question p.add(i) and *p are both inside your unsafe block. Which one
is the superpower?
Both, but for different reasons — and this matters later.
*p is superpower #1: dereferencing a raw pointer.
p.add(i) is superpower #2: <*const T>::add is declared unsafe fn, because
computing an out-of-bounds address is already undefined behaviour even if
you never dereference it. That rule surprises everyone and item 17.7 is about
it.
So unsafe { *p.add(i) } contains two unsafe operations, not one. Clippy has
a restriction lint, multiple_unsafe_ops_per_block, that would insist you
split them into two blocks with two separate justifications. It is off here —
item 17.4 turns it on, once, deliberately, because it is the problem that is
about it.
:::
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.