We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 21 of 24
Keep a vector valid when its drain is forgotten
pub struct Drain<'a, T> { /* your implementation */ }
pub fn drain_script(ops: Vec<String>) -> Vec<String>
Repair the starter's draining iterator. Calling MyVec::drain must empty the
vector immediately and yield its former elements in order, by value. Dropping
the iterator normally drops the remaining elements. Forgetting it must leave
the vector empty and usable, even though the remaining elements are leaked.
Keep the supplied command interface and support non-Copy element types.
This exercise's MyVec deliberately rejects zero-sized types.
Command behavior
Return one string per command. Commands are case-sensitive and split at the first literal space; the rest is preserved, including extra spaces.
| Command | Result |
|---|---|
push S |
Append S and return "ok"; bare push appends an empty string. |
drain_take N |
Yield at most N elements, join them with |, then drop the drain. |
drain_forget N |
Yield at most N elements, join them with |, then forget the drain. |
len |
Return the current length as decimal text. |
join |
Join current elements with ,. |
| Anything else | Return "?" without changing the vector. |
Counts parse as usize: an optional leading + is accepted; missing,
negative, malformed, whitespace-padded or out-of-range counts become zero.
Both drain commands empty the entire vector, including when N is zero.
A count larger than the length yields all elements. len and join ignore
any suffix. Separators in stored strings are not escaped.
For example, push a, push b, push c, drain_forget 1, len, join,
push d, join returns ["ok", "ok", "ok", "a", "0", "", "ok", "d"].
Here a is yielded and later dropped by the command handler. Only the
unyielded b and c are leaked.
Establish the empty state before yielding
The starter leaves the original length in the vector until Drain::drop.
A caller can use safe mem::forget to skip that destructor. The vector then
exposes slots whose values have already moved elsewhere, allowing invalid
reads or double drops. This is undefined behavior; do not rely on a particular
crash, stale length or printed result from the starter.
Record the original element range, then set the vector's length to zero before returning the drain. Keep the buffer allocated: the vector still owns it and can reuse it after the iterator's borrow ends. The drain owns the responsibility for the old elements, not for freeing the buffer.
Raw pointers alone do not express a borrow. Tie the iterator to the vector
with PhantomData<&'a mut MyVec<T>>, so safe code cannot mutate or destroy the
vector while the iterator is still used. Each successful next reads one
element and advances the cursor; Drop consumes only the unyielded range.
If the drain is forgotten, the vector remains empty. The unyielded values receive no destructor call, and later pushes can overwrite their old slots. This tradeoff is called leak amplification: forgetting a guard can leak additional resources, but must not make subsequent safe operations unsound. See the Rustonomicon's drain example.
Account for panicking destructors
A generic T::drop can panic. In the reference design, the cursor advances
before the yielded value is dropped, and the vector's length is already zero.
If cleanup unwinds, it can leave remaining elements undropped without leaving
moved-out elements accessible through the vector. Memory safety is required;
complete cleanup and restoring the original contents are separate promises.
A destructor panic does not always abort: a panic during an existing unwind can abort the process. Avoid making memory safety depend on a particular panic strategy or on cleanup reaching its final statement.
What the checks establish
Tests check command results, empty-vector behavior and reuse after a forgotten drain. The harness also allocates temporary strings, but this does not detect leaks or prove unsafe code sound. Review pointer ranges, ownership transfers, borrow lifetimes and panic paths as well as passing tests and Clippy.
Rust permits safe code to skip destruction, so an unsafe abstraction must
tolerate it. See mem::forget's safety contract.
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.