Skip to content

← Unsafe and Soundness step 4 of 24

Easy Primitives

Safety comments as a discipline: // SAFETY: and # Safety

Copy a byte range into a new vector, documenting why each unsafe operation meets its requirements.

pub unsafe fn copy_run(src: &[u8], from: usize, len: usize) -> Vec<u8>

Copy len bytes starting at from, preserving their order. For example, src = [1, 2, 3, 4, 5], from = 1 and len = 3 produces [2, 3, 4].

The caller must guarantee that from + len does not overflow and is at most src.len(). The harness supplies valid ranges, including empty ranges at the end and an empty source. Invalid ranges are outside this unsafe API's contract; the function does not need to return an error for them.

Use Vec::with_capacity, ptr::copy_nonoverlapping and Vec::set_len. For this exercise, do not use indexing, extend_from_slice or to_vec to perform the copy. Keep the supplied lint settings enabled. Output tests cannot enforce every implementation choice or establish soundness.

Document the caller's responsibilities

A public unsafe function needs a # Safety section explaining what its caller must establish. For copy_run, state the range and addition-overflow requirements. This is a caller obligation, not a claim that the function checks the arguments itself.

Unsafe traits also need documented requirements for implementations and use. The relevant contract depends on the API; do not copy a generic safety paragraph without checking what it promises.

Explain why each operation is valid here

Put a // SAFETY: comment immediately before each unsafe block. Explain how this code meets that operation's requirements, using the caller's contract and facts established by the implementation.

For example, the final length update needs both initialized bytes and sufficient capacity:

// SAFETY: the copy initialized the first `len` bytes, and
// with_capacity(len) reserved space for at least that many bytes.

A comment such as // SAFETY: this is fine gives a reviewer nothing specific to verify. A detailed comment is useful evidence of the intended argument, but the comment itself does not prove the code correct. It can be mistaken or become outdated when code changes.

If a requirement cannot be justified, investigate before proceeding. The justification may come from a check in this function, a caller guarantee or a type invariant. A check in another function is not automatically wrong; what matters is whether its guarantee still applies at this operation.

Understand the lint feedback

The starter enables several Clippy lints, and the grader treats warnings as errors:

Lint What to address
missing_safety_doc Document the public unsafe function's caller obligations.
undocumented_unsafe_blocks Add a safety justification for each unsafe block.
multiple_unsafe_ops_per_block Separate unsafe operations into individual blocks for this exercise.
unnecessary_safety_comment Avoid using a safety marker for an operation that needs no unsafe justification.
unnecessary_safety_doc Do not attach an unsafe caller contract to a safe function.

missing_safety_doc is enabled by default; the other listed lints are explicitly enabled in the template. Lints check code structure and comment presence, not the truth of the safety argument. Exact diagnostics and comment recognition can vary with the Clippy version. Keep comments adjacent to the operations they explain rather than relying on formatting edge cases.

A nested call can contain more than one unsafe operation. For example, from_raw_parts_mut(ptr.add(mid), n) calls both the unsafe pointer-offset method and the unsafe slice constructor. Separate them when this lint is enabled, and justify the requirements of each. Splitting a block does not by itself make either operation sound.

src.as_ptr() and out.as_mut_ptr() are safe calls and do not need safety markers. However, raw-pointer operations are not all safe until dereference: add already has requirements when the new pointer is computed.

Justify the three steps

Compute the source pointer. The caller's range promise gives from <= src.len(). For a nonzero byte offset, the original pointer and the traversed range must belong to the same allocation, and the offset must fit in isize. A valid byte slice provides the required size bound. The result may point just past the range's end when no bytes will be read. An offset of zero also handles the empty slice without requiring an allocation behind it.

Copy into spare capacity. For a nonempty copy, the source range is readable and initialized because it lies inside src. The new vector has writable capacity for at least len bytes and its allocation is separate from the source, so the ranges do not overlap. Both pointers have the alignment required for u8. The count is in elements; it equals a byte count here because the element type is u8.

For a zero-length copy, neither vector needs allocated storage. The pointers still satisfy the copy operation's alignment requirements, and both copied ranges are empty. Do not describe this case as requiring two allocations.

Set the initialized length. Call set_len(len) only after the copy. The requested length must be within capacity, and the newly included elements must already be initialized. Reserving capacity alone does not initialize them.

Do not reverse the copy and length update, even if tests appear to pass. That violates set_len's contract. For element types with destructors, incorrect initialization tracking can also make cleanup access invalid elements. This exercise copies u8; generalizing a bitwise copy to arbitrary owning types requires a separate ownership argument.

For more context, see validity and safety invariants and leak amplification. Passing compilation, tests and Clippy supports the review; it does not replace checking the unsafe contracts.