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`
pub fn rotate_via_ptr(v: Vec<u8>, k: usize) -> Vec<u8>
Rotate v left by k positions into a new buffer, using only raw-pointer
reads and writes. [1,2,3,4,5] rotated by 2 is [3,4,5,1,2].
Rules: build the output with Vec::with_capacity, fill it with
<*const u8>::read and <*mut u8>::write, publish it with set_len. No
slice indexing, no rotate_left, no copy_from_slice, no iterators over the
input. k may be zero, equal to the length, or much larger than the length;
an empty input rotates to an empty output.
The starter has two bugs on one line. One is an E-code; one is silent.
Four guarantees you just dropped
A &T carries four promises that a *const T does not.
It may be null. A reference never is; a raw pointer can be, and dereferencing null is undefined behaviour.
It may dangle. Nothing ties a raw pointer’s lifetime to anything. The allocation it points into can be freed, moved by a reallocation, or never have existed. The compiler will not stop you and will not tell you.
It ignores the borrowing rules entirely. You may have as many *mut T to
one place as you like, alongside any number of &T. The borrow checker does
not track raw pointers. (This is precisely why they are useful — and precisely
why the obligation moves to you. Item 17.13 is about what you still owe.)
It has no Drop, no cleanup, no ownership. Copying a *mut T copies an
address, nothing more. Two pointers to one allocation is not “shared
ownership”, it is two ways to make the same mistake.
::: question Which of these needs unsafe: creating a raw pointer, casting one
to a different pointer type, comparing two, or dereferencing one?
Only the last.
Creating a raw pointer is safe — it is arithmetic on an address, and nothing
observable happens. Casting between pointer types is safe. Comparing them is
safe. Even p.is_null() and p.addr() are safe.
Dereferencing is superpower #1, and it is the only one of the four that
needs unsafe. This asymmetry is why so much pointer plumbing in real code
carries no unsafe at all: you can pass raw pointers around, store them in
structs, offset them (well — add is unsafe, see item 17.7) and hand them to
C, and the unsafe appears only at the handful of places where you actually
touch memory.
It is also why “how much unsafe code is in this function?” is the wrong question. Count the dereferences, not the pointers. :::
*const versus *mut is documentation, not enforcement
This one catches people. The const/mut distinction on raw pointers is a hint to readers and to the type checker. It is not a runtime or aliasing guarantee:
let x = 5i32;
let p = &x as *const i32 as *mut i32; // compiles
unsafe { *p = 6 }; // undefined behaviour
The cast is legal. The write is not, because the original pointee is
immutable. rustc catches the obvious form of this with the deny-by-default
lint invalid_reference_casting:
error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
Take that suggestion seriously — UnsafeCell (item 17.15) is the only
sanctioned way to mutate through a shared reference, and every other route is
a bug wearing a cast.
rustc also ships dangling_pointers_from_temporaries, which warns when a
*const T is derived from a temporary:
let p = &String::from("hi") as *const String; // the String dies at the `;`
Writing this one
Vec::as_ptr and Vec::as_mut_ptr give you the two ends. out.as_mut_ptr()
is valid for capacity elements even though out.len() is zero — capacity is
what the allocation actually has; length is what the vector claims is
initialised. Keeping those two ideas apart is most of what set_len is for.
Note the guard: k % n divides by zero when n is zero, so handle the empty
case before you compute the shift. And i + shift can exceed n, so wrap it
back — an unwrapped index reads past the end of the source allocation, which
is UB even though this grader will happily hand you a plausible-looking byte.
::: question out has length 0 and capacity n. Is
out.as_mut_ptr().add(n - 1) a valid place to write?
Yes — writing there is fine; reading it back through the Vec before
set_len is not.
with_capacity(n) performs the allocation. All n bytes exist and are owned
by that Vec. Writing to them through the raw pointer is legal: the memory is
yours, it is just not yet initialised from the type system’s point of view.
What is not legal is letting anything treat those bytes as a u8 before you
put a value there — and len is exactly the field that says “these bytes are
values”. That is why set_len(n) comes last, after every slot has been
written. If it came first, there would be an interval in which a perfectly
ordinary Vec<u8> claimed to contain n initialised bytes that did not
exist. For u8 you would get away with it; item 17.12 explains why “getting
away with it” is the wrong standard.
:::
What this grader cannot see
If you drop the wraparound and read src.add(i + k) past the end, several of
these cases still pass — the allocator’s next bytes are readable and the
numbers come back. That is undefined behaviour, and the only thing standing
between it and a miscompile is that LLVM has not yet had a reason to exploit
it. Nothing here will tell you. The big 4096-byte case is there to make an
off-by-one likely to show up as a wrong answer, not certain to.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.