We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 17 of 24
Extend a vector with insertion and consuming iteration
Extend the supplied MyVec<T> with insertion, removal, slice access and an
iterator that takes ownership of the vector's elements.
pub fn vec2_script(ops: Vec<String>) -> Vec<String>
Implement insert, remove, Deref<Target = [T]>, DerefMut, and
IntoIterator<Item = T> with an IntoIter<T> type. Support non-Copy values
without cloning them. Keep the supplied allocation strategy and script
interpreter. This teaching vector explicitly rejects zero-sized types in
new; supporting them is outside this exercise.
Operations and output
The script starts with an empty MyVec<String> and returns one string for
every command. Invalid commands or indices leave the vector unchanged except
for drain_take, which always consumes it.
| Command | Output and effect |
|---|---|
push S |
Append S; return "ok". |
insert I S |
Insert before index I, allowing I == len; return "ok", or "err" for an invalid index. |
remove I |
Remove and return the indexed string; return "none" if the index is invalid or I >= len. |
sort |
Sort strings using Rust's string ordering; return "ok". |
join |
Join current elements with commas. |
len |
Return the number of elements as decimal text. |
drain_take N |
Consume the vector, join up to its first N elements with a vertical bar, and discard the rest. |
| Unknown command | Return "?". |
After drain_take, the script has a fresh empty vector. Taking zero still
discards every old element; taking more than the length returns all elements.
Joining an empty vector produces an empty string.
The parser splits the command at its first literal ASCII space. push keeps
the remainder as its string. insert splits that remainder once more into
index and string. Missing string text means an empty string. Spaces within
strings are preserved; input is not trimmed or normalized. sort, join and
len ignore any remainder.
Indices and counts use Rust's usize parser, which accepts a leading +.
Malformed or unrepresentable insert/remove indices are invalid. A malformed
or unrepresentable drain_take count defaults to zero.
commands: ["push a", "push b", "insert 1 x", "remove 0", "join", "drain_take 1", "len"]
results: ["ok", "ok", "ok", "a", "x,b", "x", "0"]
The public insert and remove methods themselves should panic on invalid
indices before changing the vector. The interpreter checks first to produce
the error strings above.
Shift values without duplicating ownership
Insertion shifts the suffix one slot right before writing the new value.
Removal reads the removed value before shifting the following elements left.
Use ptr::copy for these shifts: the ranges can overlap. A shift of zero or
one element need not overlap, but the implementation must handle longer shifts
too. Undefined behavior from a wrong copy primitive has no guaranteed symptom.
Raw copying duplicates bytes, not ownership rights. Treat the old source slots
as moved from where appropriate. After removal, the old final slot is outside
the initialized prefix and must not be dropped again. Keep len consistent
with which values the vector still owns. The raw read/copy/write operations
here do not invoke an element's Clone or Drop implementation.
See ptr::copy for its overlap and validity requirements. Retain safety comments explaining the ranges used.
Expose only initialized elements
Deref returns a shared slice of length len; DerefMut returns a mutable slice
of the same length. Capacity includes spare storage that must not be exposed
as initialized T values. The empty vector's dangling sentinel is non-null
and aligned, as required even for an empty slice.
Shared slice methods become available through Deref when their type bounds
hold. Mutating methods such as sort also require DerefMut. The script's
join and sort calls exercise these two forms of access.
Transfer the allocation to IntoIter
Consuming iteration transfers responsibility for the allocation from MyVec to IntoIter. Suppress the original vector's destructor with ManuallyDrop before transferring its pointer, length and capacity. See ManuallyDrop.
Track the unyielded range separately from the allocation capacity. Each
next() reads one value and advances the start; that value belongs to the
caller. Exhaustion returns None on subsequent calls. Report the remaining
element count in both parts of size_hint.
On iterator destruction, drop only unyielded elements, then release the buffer using its original capacity-based layout. For four stored elements in an allocation of capacity eight, taking two leaves two values to destroy but an eight-slot allocation to free. Never infer capacity from the remaining count. An iterator with zero capacity has no allocation to deallocate.
Check cleanup separately
Test front, middle and end insertion/removal, empty vectors, repeated growth, full consumption and partial consumption. A non-Clone value with a destructor counter can reveal missed or repeated destruction that string outputs miss.
The supplied cleanup loops can leak remaining resources if an element's destructor panics. They are a teaching implementation, not a complete match for standard Vec's cleanup behavior. Tests with nonpanicking destructors do not establish panic cleanup or general soundness.
The runner requires compilation, tests and Clippy with warnings denied. Its extra allocations after the script may expose memory bugs, but they do not reliably detect leaks or prove that unsafe code is correct.
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.