We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 4 of 24
Safety comments as a discipline: `// SAFETY:` and `# Safety`
pub unsafe fn copy_run(src: &[u8], from: usize, len: usize) -> Vec<u8>
Copy len bytes out of src, starting at index from, into a fresh
Vec<u8>. Build the vector with Vec::with_capacity, fill it with
ptr::copy_nonoverlapping, and publish it with Vec::set_len — no
extend_from_slice, no to_vec, no indexing.
The interesting part is not the copy. It is that this template makes the safety discipline a compile gate, and the starter fails it four different ways.
Two comments, two different jobs
Every unsafe fn and every unsafe trait gets a # Safety section in
its doc comment. It states the obligations the caller must meet. It is a
proof obligation, written outward, for a stranger.
Every unsafe {} block and every unsafe impl gets an immediately preceding
// SAFETY: comment. It states why those obligations are met here. It
is a proof discharge, written inward, for the next person to touch this
function — usually you, in eight months.
They should read as such. A good // SAFETY: comment names the specific
preconditions that specific earlier lines established:
// SAFETY: `mid <= len` was asserted above, so both ranges lie inside the
// same allocation and are disjoint.
A bad one reassures:
// SAFETY: this is fine, we checked it
The second one is worse than no comment, because it looks like an audit.
::: question Why is “I couldn’t write the safety comment” the most reliable bug signal in unsafe code? Because the comment is the proof, and if the proof does not exist the code is unsound.
Writing a safety comment forces you to enumerate the preconditions of every operation in the block and point at the line that establishes each one. When you sit down to do that and find there is no such line — the check happens in a different function, or it happens after, or it does not happen at all — you have found the bug before it shipped.
This is why experienced reviewers read the safety comments first and the code second. The comment tells them what to verify. Code with no comment tells them nothing, so the only honest review is to re-derive the whole argument from scratch, which nobody does. :::
Three lints, on deliberately, and one authoring gotcha each
clippy::missing_safety_doc — on by default. A pub unsafe fn without a
# Safety section is an error. Write what the caller must guarantee:
from + len must not overflow and must be <= src.len().
clippy::undocumented_unsafe_blocks — enabled by the template. Every
unsafe {} needs a // SAFETY: comment. The gotcha: the comment must be on
the line or lines directly preceding the block. A blank line between the
comment and the unsafe breaks detection and the lint fires anyway.
clippy::multiple_unsafe_ops_per_block — enabled by the template, and
much stricter than it reads. It counts operations, not statements:
unsafe { std::slice::from_raw_parts_mut(ptr.add(mid), n) }
is two unsafe operations — the call to the unsafe function
from_raw_parts_mut, and the call to the unsafe method add. That block
fails. You must hoist:
// SAFETY: ...
let p = unsafe { ptr.add(mid) };
// SAFETY: ...
let s = unsafe { std::slice::from_raw_parts_mut(p, n) };
This is why the lint is restriction rather than a default: turned on
everywhere, every pointer-arithmetic function becomes an exercise in let
bindings. It is on here, in the one problem that is about it, so that you
feel what one-justification-per-operation actually costs. Later problems in
this track leave it off.
clippy::unnecessary_safety_comment is also on, and it grades the reverse
direction: a // SAFETY: comment attached to a block or statement that is not
unsafe at all. A comment not attached to a real obligation is noise that
trains readers to skip safety comments, which is the habit this whole
discipline exists to prevent.
::: question let dst = out.as_mut_ptr(); — does that line need a // SAFETY:
comment?
No, and adding one is an error here.
Vec::as_mut_ptr is a safe function. Creating a raw pointer is always safe —
it is just an address. Nothing can go wrong until somebody dereferences it,
and that is the operation that needs the justification.
clippy::unnecessary_safety_comment will reject a // SAFETY: on that line.
The rule it is enforcing: a safety comment marks a place where a proof
obligation was discharged. Sprinkling them on safe code destroys that signal.
:::
The three unsafe operations you actually need
ptr.add(from) — offsetting a pointer. Unsafe because computing an
out-of-bounds address is undefined behaviour at the arithmetic, before any
dereference. Its precondition: the result must stay inside the same
allocation, or be exactly one past its end.
ptr::copy_nonoverlapping(src, dst, count) — a memcpy. Its preconditions:
both pointers valid for count elements, properly aligned, and the two ranges
must not overlap. count is in elements, not bytes. Here the source
is inside src‘s allocation and the destination is inside a freshly allocated
buffer, so disjointness is free — say so in the comment.
Vec::set_len(len) — publishing initialised memory. Its precondition:
len <= capacity and the first len elements are actually initialised.
This is the one that repays a careful comment, because it is the line that
turns “some bytes I wrote” into “a Vec<u8> the rest of the program will
trust”.
Ordering matters, and the harness cannot check it
Set the length after the copy, never before. If you set_len(len) first
and then copy, there is a window in which a Vec<u8> exists whose length
claims len initialised bytes that are not initialised. For u8 you will
almost certainly get away with it; for a type with a destructor a panic in
between would run destructors on garbage.
Nothing in this grader will catch that. Every test here passes with the statements in the wrong order. Item 17.12 explains why “briefly invalid” is not a thing, and item 17.21 explains what a panic does to a half-built structure.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.