We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 7 of 24
Pointer arithmetic: offset, add, sub, wrapping_*
Find the maximum value in each consecutive window of a vector using raw pointers.
pub fn windows_max(v: Vec<i64>, w: usize) -> Vec<i64>
If w == 0 or w > v.len(), return an empty vector. Otherwise return
v.len() - w + 1 maxima, ordered by each window's starting position.
For w == 1, return the input values; for w == v.len() on nonempty input,
return one maximum. Empty input always produces an empty output.
v = [1, 3, 2, 5, 4], w = 2 -> [3, 3, 5, 5]
v = [-9, -2, -7], w = 2 -> [-2, -2]
Values may include i64::MIN and i64::MAX. Only compare values; do not
perform arithmetic on them. Repeated maxima remain repeated in the output.
Use raw-pointer traversal, a pointer to the end of the initialized elements,
and offset_from_unsigned to count the elements remaining. Do not use indexing
or slice::windows in your solution. These are exercise requirements; output
tests alone do not enforce the implementation approach.
Distinguish initialized elements from allocated capacity
v.as_ptr() points to the vector's first element. For a nonempty vector,
start.add(v.len()) marks the end of its initialized elements. Use this as
the loop sentinel and do not read from it.
The vector may have spare capacity beyond its length. In that case, this
sentinel is still inside the allocation, rather than one past the allocation.
Spare capacity does not contain additional initialized i64 values that the
algorithm may read.
This distinction affects where a bad traversal first violates a rule.
Reading past len is invalid even if the pointer arithmetic stayed inside
allocated capacity. Arithmetic that leaves the allocation's permitted range
can itself be undefined behavior, before a dereference occurs.
Choose the arithmetic operation deliberately
| Method | Unit and purpose |
|---|---|
add(n) / sub(n) |
Move forward/backward by n elements, using a usize. |
offset(n) |
Move by a signed number of elements. |
byte_add(n) / byte_offset(n) |
Move by bytes rather than elements. |
offset_from(origin) |
Compute signed distance in elements. |
offset_from_unsigned(origin) |
Compute a nonnegative distance in elements. |
wrapping_add(n) / wrapping_offset(n) |
Compute a pointer without the same in-bounds arithmetic requirement. |
The non-wrapping offset operations are unsafe. For nonzero offsets, their
allocation, byte-offset and address-overflow requirements must hold even if
you never dereference the result. Within an allocation of exactly ten i64
slots, a pointer to slot zero may advance by ten to the one-past-end sentinel,
but not by eleven. The one-past-end pointer is not readable.
wrapping_* operations are safe to compute, but do not grant permission to
dereference an invalid address. They preserve the originating pointer's
provenance; producing an address numerically equal to another allocation does
not make it a pointer that may access that allocation. For this exercise,
ordinary add operations with maintained bounds are sufficient.
Maintain a complete-window invariant
Let lo be the start of the candidate window and end the end of the
initialized elements. Both derive from the same vector allocation.
- Compute
end.offset_from_unsigned(lo). - Stop if fewer than
welements remain. - Read exactly
winitialized elements fromlo, updating the maximum. - Advance
loby one element and repeat.
The distance operation is unsafe too. Here end and lo share an allocation,
lo <= end, and their byte distance is an exact multiple of size_of::<i64>()
within the permitted range. i64 is not a zero-sized type. An address
comparison alone would not establish all of these conditions.
Handle invalid window sizes before starting traversal. In each accepted
window, initialize the maximum to its first value or to i64::MIN, not zero.
Repair the starter's two separate problems
Its while lo < end condition visits every initialized starting position,
even when too few elements remain for a full window. For w > 1, the trailing
iterations read beyond the initialized range. They may also compute pointers
beyond the allocation, depending on the width and capacity.
For example, at the last element with w == 2, computing the next pointer
may still be permitted, but reading through it is not. It is inaccurate to
describe every failure as an out-of-bounds arithmetic operation.
Separately, Clippy flags lo.offset(i as isize) with ptr_offset_with_cast.
For this nonnegative element offset, lo.add(i) avoids the unnecessary cast.
Fixing that lint does not repair the loop bounds. The lint also applies when
w == 1, where the starter's traversal otherwise produces the right windows.
Explain the unsafe blocks
Each read needs an aligned pointer to a live, initialized element. Keep the vector alive and do not mutate or reallocate it while traversing. Each advance must remain within the bounds justified by the current window or sentinel. Each distance calculation must use related pointers and the correct direction.
State these facts in the safety comments rather than writing only “in bounds.”
The reference approach checks every element of every window, so its runtime
is proportional to (len - w + 1) * w; this task teaches pointer invariants,
not the linear-time sliding-maximum algorithm.
A passing submission must compile, pass the tests, and pass
clippy -D warnings. Correct outputs do not prove absence of undefined
behavior. Tools such as Miri can detect many violations on executed paths,
but neither a passing test nor a particular observed output establishes safety
for all inputs.
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.