We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 2 of 24
What unsafe allows, and what it does not change
pub fn sum_via_raw(v: Vec<i64>) -> i64
pub fn sum_safe(v: &[i64]) -> i64
Write the same sum twice.
sum_via_rawtakes ownership of the vector, gets a raw pointer to its first element withv.as_ptr(), and reads elementithroughp.add(i).sum_safeborrows a slice and must contain nounsafecode.
Both functions receive the same values, in the same order, and must return the
same result. The vector may be empty (the sum is then 0) and may contain any
i64, including i64::MIN and i64::MAX. Add with wrapping_add, so a sum
that does not fit in i64 wraps around in two's complement instead of
panicking in a debug build. For example, [i64::MAX, 1] sums to i64::MIN.
The grader prints both results as {"raw": ..., "safe": ...}.
The starter does not compile. It fails with E0133, the error for an unsafe
operation used outside an unsafe block or unsafe fn. Read that error
before changing anything.
What unsafe allows
Most Rust code is safe Rust, code that does not use unsafe. If safe code
compiles, it cannot cause undefined behavior (a program state the language
gives no meaning to, such as reading freed memory), provided the unsafe code
it calls into, including the standard library's, is correct. A few operations
cannot be checked that way. Rust only accepts them inside an unsafe block or
unsafe fn, and the programmer becomes responsible for the conditions the
compiler cannot verify.
The Rust Book lists five such operations:
- dereference a raw pointer (
*const Tor*mut T); - call an
unsafe fn, including most functions declared in anexternblock; - read or write a mutable
static; - implement an
unsafe trait(writtenunsafe impl); - read a field of a
union(assigning to a field is safe).
The Rust Reference, which is the more complete source, also lists:
- call a safe function marked
#[target_feature]from a function that does not enable the same features; - declare an
externblock, which edition 2024 requires to be writtenunsafe extern "C" { ... }; - apply an unsafe attribute such as
#[unsafe(no_mangle)]or#[unsafe(export_name = "...")], which edition 2024 also requires to be markedunsafe.
Check the Reference when you need the exact current list. The exercise below uses only the first two.
What unsafe does not change
It does not turn off the borrow checker. This fails inside unsafe
exactly as it would outside it:
let mut x = 5;
unsafe {
let a = &mut x;
let b = &mut x; // error[E0499]: cannot borrow `x` as mutable more than once at a time
*a += 1;
*b += 1;
}
a is still used after b is created, so two mutable references to x are
live at once. The compiler also warns that this unsafe block is unnecessary,
because nothing inside it is an unsafe operation.
It does not turn off type checking. let x: u32 = "hello"; is still
error E0308 (mismatched types) inside unsafe.
It does not turn off lifetime checking. Returning a reference to a local
variable, as in fn f() -> &'static i32 { let x = 1; &x }, is still error
E0515 with the body wrapped in unsafe.
It does not make incorrect code correct. unsafe does not grant
permission to break Rust's rules. It moves the job of upholding them from the
compiler to you. If the conditions are not met, for example a raw pointer
points outside its allocation, the behavior is undefined: the program may
appear to work, crash, or be optimized in ways that make its output
meaningless.
Check your understanding A function fails to compile with E0499. Someone wraps the Show answer Hide answer
offending lines in unsafe { }. What happens?
Nothing changes. The program still fails with E0499.
unsafe only permits the specific operations listed above. The borrow
checker, type checker and lifetime checks apply inside unsafe blocks with the
same rules as everywhere else.
Borrow errors such as E0499, E0502, E0505, E0515 and E0597 are usually fixed by
restructuring the code: shortening a borrow, splitting a slice with
split_at_mut, using a type such as RefCell that checks borrows at run time,
or changing a function signature.
Why the exercise has two functions
sum_safe is one short line with .iter() and .fold(). It needs no
unsafe, because a slice iterator stays within the slice for you.
sum_via_raw computes the same value, but every read is an operation whose
safety you have to argue for yourself. The comparison shows that this task
never needed unsafe. Code that uses unsafe should have a reason that safe
Rust cannot meet, such as calling a C function or managing raw memory inside a
data structure, and each unsafe block adds conditions someone has to check.
The lints in this problem
The grade is: the code compiles, the tests pass, and cargo clippy -- -D warnings reports nothing. -D warnings turns every warning into an error.
The starter enables clippy::undocumented_unsafe_blocks with
#![warn(...)]. Every unsafe { } block needs a comment starting with
// SAFETY: above it that explains why the operation is valid. The comment
must come directly before the statement containing the block; a blank line in
between is accepted, but another line of code is not.
Item 17.4
covers how to write these comments well.
The compiler's own unused_unsafe lint warns about an unsafe block that
contains no unsafe operation. A block that wraps the whole loop does contain
one, so that lint stays quiet. Wrap only the expression that needs unsafe
anyway: the smaller the block, the less code the SAFETY comment has to
justify.
Check your understanding
In unsafe { *p.add(i) }, which operations need unsafe?
Show answer
Hide answer
Both p.add(i) and the * dereference.
* on a raw pointer is operation 1: dereferencing a raw pointer.
p.add(i) is operation 2: <*const T>::add is declared unsafe fn. Its
documented requirement is that the resulting pointer stays within the same
allocation (or one element past its end). Computing an address outside that
range is undefined behavior even if the pointer is never dereferenced.
wrapping_add is the safe method for offsets that may leave the allocation,
but dereferencing its result still requires unsafe and a valid pointer.
Item 17.7
covers pointer arithmetic in detail.
So unsafe { *p.add(i) } contains two unsafe operations. The Clippy
restriction lint multiple_unsafe_ops_per_block would require two separate
blocks, each with its own justification. It is not enabled here;
item 17.4
enables it.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.