We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 5 of 24
Raw pointers: *const T and *mut T
Implement rotate_via_ptr(v: Vec<u8>, k: usize) -> Vec<u8>.
Rotate the bytes left by k positions into a new output buffer.
For [1, 2, 3, 4, 5] and k = 2, return [3, 4, 5, 1, 2].
Reduce the shift modulo the input length: k = 0 and k = 5 leave
this input unchanged, while k = 7 has the same result as k = 2.
Empty input produces empty output for any shift.
Allocate the output with Vec::with_capacity, read each byte through a
raw pointer, write it through the output pointer, and set the output length
after initialization. Do not use slice indexing, iterators over the input,
rotate_left or copy_from_slice in your implementation.
A pointer does not keep its target alive
A raw pointer can be null or dangling. Its type does not keep an allocation alive, establish ownership or guarantee that a dereference is valid. Copying or dropping the pointer does not copy or free its target.
In this function, the input Vec owns the source bytes and the output Vec owns the destination allocation. Keep both vectors alive and avoid any operation that could reallocate their buffers while using the pointers.
Raw pointers also do not remove aliasing rules. The compiler does not track their uses like ordinary borrows, but unsafe accesses must still obey the applicable validity, alignment, initialization and aliasing requirements.
Separate pointer creation from unsafe access
v.as_ptr() and out.as_mut_ptr() are safe ways to obtain these
pointers. Checking is_null(), inspecting an address and casting between
these pointer types do not themselves access the pointee.
Pointer arithmetic with add, reading through a raw pointer, writing
through one and calling Vec::set_len all require unsafe blocks here.
An unsafe block records your responsibility for their preconditions; it
does not validate them at runtime.
Other operations, including foreign calls, can have unsafe requirements too. Counting only dereferences is not enough to review an unsafe function.
Mutability is still a real restriction
*const T and *mut T expose different operations, but a cast from one
to the other does not grant permission to write. A pointer derived from a
shared reference cannot be used to mutate ordinary data behind that
reference just because its type was changed to *mut T.
UnsafeCell provides the language's mechanism for interior mutation
through shared access. It does not remove requirements such as avoiding
data races and respecting exclusive references.
This rotation does not need UnsafeCell: the source is read-only and the newly allocated destination is exclusively owned.
Keep the source index in range
Return early when n == 0 before calculating k % n. Otherwise,
compute shift = k % n once.
For each destination index i from zero through n - 1, choose the
source index i + shift, subtracting n if necessary. This gives a
source index below n. Normalize k before adding it; adding an
arbitrary usize shift directly can overflow.
The starter neither wraps its source index nor supplies the unsafe blocks.
It also calls the unsafe set_len method outside an unsafe block.
Fixing only the compiler errors would still leave out-of-bounds accesses
for nonzero shifts.
Capacity and initialization are different
Vec::with_capacity(n) creates a vector with length zero and capacity
at least n. The reserved slots are available for writes but are not
initialized elements yet.
Write one valid byte into every output slot in 0..n. Afterward,
set_len(n) makes that initialized prefix visible as the vector's
elements. The required capacity condition is n <= out.capacity(),
not equality.
Setting the length before initialization violates set_len's contract, including for u8. Having no invalid bit patterns does not make uninitialized memory a valid byte.
Once you initialize a slot, it can be read through a valid raw pointer before publishing the vector length. Safe slice access through the vector still only exposes the range below its recorded length.
Check temporary lifetimes rather than guessing
Some borrowed temporaries have their lifetime extended by the enclosing let statement. This complete example is valid:
fn main() {
let pointer = &String::from("hi") as *const String;
// SAFETY: this borrow expression extends the temporary String's
// lifetime to this block; it has not been mutated or moved.
let length = unsafe { (&*pointer).len() };
assert_eq!(length, 2);
}
A different expression, such as String::from("hi").as_ptr(), does not
keep the temporary String alive after the statement. Do not dereference
that resulting dangling pointer. Prefer a named owner when the lifetime
would otherwise be unclear.
What the tests establish
Output cases exercise wrapping, empty input and a larger buffer. They cannot prove that an unsafe implementation is sound. An invalid access may crash, produce incorrect output or appear to work; no particular outcome is guaranteed.
Explain each unsafe block in terms of live allocations, in-bounds indices, initialized source values and valid destination writes. Here the nonempty source and destination allocations are distinct, and no conflicting references are kept alive during those writes.
The submission must compile, match the expected bytes and pass Clippy with warnings denied.
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.