We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 16 of 24
Build a vector with allocation, push and pop
Implement an owning vector using the supplied pointer, length and capacity fields. Store elements in manually allocated memory and expose safe operations.
pub struct MyVec<T> { ptr: NonNull<T>, len: usize, cap: usize }
pub fn vec_script(ops: Vec<String>) -> Vec<String>
Complete grow, push, pop, get and Drop. Add Default, is_empty,
and the bounded Send and Sync implementations described below. Support
non-Copy values without cloning them. Keep the supplied script interpreter.
new deliberately rejects zero-sized types; you do not need to support them.
Required behavior
A new vector has length and capacity zero. Its first push allocates four
slots. Whenever a push finds len == cap, double the capacity before writing.
Popping elements, including the last one, does not reduce capacity.
push appends a value. pop transfers ownership of the last value to the
caller, or returns None when empty. get(index) returns a reference tied to
the vector borrow, or None when index >= len. is_empty reports len == 0,
and Default produces the same empty vector as new.
The script starts with an empty MyVec<String> and returns one string per
operation:
| Command | Output |
|---|---|
push S |
Append S and return "ok". |
pop |
Return the last string, or "none" when empty. |
len |
Return the number of elements as decimal text. |
cap_at_least N |
Return "true" if capacity is at least N, otherwise "false". |
get I |
Return the indexed string, or "none" when missing. |
| Unknown command | Return "?". |
The parser splits at the first literal ASCII space. A push stores the entire
remainder without trimming, including an empty string when no remainder is
present. pop and len ignore the remainder. Numeric fields use Rust's usize
parser, including a leading +. Invalid or unrepresentable capacity thresholds
default to zero; invalid indices give "none". Empty scripts return no results.
A stored string "none" is indistinguishable from the missing-value marker in
this output format.
commands: ["cap_at_least 1", "push a", "cap_at_least 4", "cap_at_least 5", "pop", "cap_at_least 4"]
results: ["false", "ok", "true", "false", "a", "true"]
Track initialized storage
Maintain len <= cap. Slots before len contain owned, initialized values.
Slots from len to cap are spare storage and must not be read or dropped as
values. With zero capacity, use NonNull::dangling() as a non-null, aligned
sentinel; there is no allocation to free.
NonNull<T> is covariant in T. An owning collection can remain covariant even
when it provides mutable access through &mut self: that exclusive borrow
prevents conflicting use during mutation. Returning mutable access does not
automatically require making the owning type invariant. See the
Rustonomicon's variance explanation.
Allocate and grow
Construct layouts with Layout::array::<T>(capacity). Use alloc for the
first nonempty buffer and realloc with the old layout and new byte size for
later growth. On successful reallocation, use the returned pointer; the old
pointer must no longer be used. Update capacity to match that allocation.
Layout construction rejects arithmetic overflow and layouts exceeding its isize-based size bound. The supplied explicit size assertion is an additional statement of the invariant, not a size check missing from Layout::array. Reject impossible growth before changing ownership state. See Layout::array.
Route a null allocation result to alloc::handle_alloc_error. It does not
return normally. Avoid assuming that its termination behavior is always the
same under every environment and configuration; see
handle_alloc_error.
Move values into and out of slots
After ensuring capacity, push writes the new value with ptr::write, then
increments len. Writing this way does not attempt to drop an old value in
uninitialized storage.
For a nonempty pop, decrement len and read the former final slot with
ptr::read. The returned value belongs to the caller; the vector must not
drop that slot again. Raw reads and writes do not invoke Clone. The remaining
bytes beyond len are no longer an owned element of the vector.
Destroy elements, then release the buffer
Drop the initialized values and deallocate with the layout for the current capacity. Never deallocate the dangling sentinel, and never run destructors over spare capacity. A pop loop is one simple cleanup strategy; it depends on correct bookkeeping just like any other implementation.
With nonpanicking destructors, each retained element must be dropped exactly once. The reference's simple loop can leak the remaining elements and buffer if an element destructor panics. This exercise does not reproduce all of standard Vec's panic-cleanup machinery.
Justify thread bounds
NonNull does not make its containing type Send or Sync automatically. For
this owning API, implement Send when T: Send and Sync when T: Sync, with
safety comments explaining ownership and shared access. Do not omit the bounds:
a shared or moved collection must respect the restrictions of its elements.
The script checks values, lengths and capacity thresholds. It does not prove allocation correctness or soundness. Also test non-Clone elements with destructor counters, out-of-range access and reuse after popping to empty. Passing requires compilation, test cases and 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.