We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 8 of 24
ptr::read, write, copy, copy_nonoverlapping, drop_in_place
Implement dedup_in_place(v: Vec<String>) -> Vec<String> using the vector's
raw buffer. Keep the first string in each run of consecutive equal strings.
For ["a", "a", "b", "b", "a"], return ["a", "b", "a"]. Equal strings
separated by a different string remain separate. Preserve order and handle
empty input, a single element and empty strings.
Do not call retain or dedup, build a second vector, or clone the
survivors. Move survivors within the existing buffer and drop each removed
string exactly once. String equality compares contents; the strings need
not share an allocation.
Track the live elements
Use a read index to scan forward and a write index for the next survivor. With nonempty input, keep the first element and start both indices at one.
Before processing each element:
- Slots
0..writehold the survivors so far. - Slots
write..readare no longer independent owners. Their old values were either dropped or moved into the survivor prefix. - Slots
read..lenstill hold unprocessed strings.
Compare the current string with the last survivor at write - 1.
If equal, remove and drop the current string. Otherwise, move it to
write if the indices differ, then increment write.
Do not compare against read - 1: that slot may already have been
removed or moved. When the scan finishes, use set_len(write) to make
the vector's recorded length match its live prefix.
What the pointer operations do
| Operation | Effect | Ownership responsibility |
|---|---|---|
ptr::read(src) |
Returns a bitwise copy without changing the source bytes | For String, stop treating the source as a separate owner |
ptr::write(dst, value) |
Stores a value without dropping the destination | Drop or move any previous owner first if it must be released |
ptr::copy(src, dst, count) |
Copies count elements; overlap is allowed | It does not clone values or manage their destructors |
ptr::copy_nonoverlapping(src, dst, count) |
Copies count elements; ranges must not overlap | The same ownership rules apply |
ptr::drop_in_place(ptr) |
Destroys the value at that location | Do not later use or drop that value again |
The two copy functions count elements, not bytes. read, write
and drop_in_place operate on one value and have no count parameter.
Reading a String with ptr::read does not clear its old slot. You can
drop the returned String, but the original slot must then remain outside
the live set until it is overwritten. Copying a survivor similarly leaves
stale source bytes that must not be dropped as another owner.
This bookkeeping does not replace the other unsafe requirements. Reads must use live, initialized strings; source and destination ranges must remain within the vector allocation and meet the operation's alignment requirements. Temporary comparison references must end before conflicting writes. Even a zero-element copy still requires properly aligned pointers.
The starter's defects
The copy count is size_of::<String>(), so the starter copies that many
String elements, not one String. On a typical 64-bit target that means
24 elements. The actual size is target-dependent. This can exceed the
valid source or destination range and triggers Clippy's
size_of_in_element_count lint.
The starter also calls copy_nonoverlapping when write == read.
For a nonzero count, identical ranges overlap. Skip the copy when an
element is already in the right slot.
When write < read, a one-element copy between these distinct String
slots does not overlap. Either copy function can be correct under that
condition. Recheck the range argument if you later change the algorithm
to move several elements at once; do not assume that distinct starting
addresses imply disjoint ranges.
Finally, the duplicate branch does not drop the removed strings. Reducing the vector's length will not drop them for you, so this leaks their buffers.
Why the final length update needs care
During compaction, the vector's old length includes slots that are no longer live owners. Unwinding through that state could make Vec drop stale values. This concrete loop relies on String comparison and destruction not unwinding, and on doing no other fallible work before restoring the length.
Do not generalize it to arbitrary types, callbacks or fallible operations without a cleanup strategy. A guard may need to repair the vector during unwinding. The leak-amplification lesson examines ownership bookkeeping when control flow changes unexpectedly.
What passing tests establishes
The harness checks the output strings, length and a digest. It also allocates 2,000 strings afterward, which may expose some heap damage. That extra allocation is not a reliable detector of undefined behavior.
Output tests alone do not establish that duplicates were freed or that the unsafe implementation is sound. Memory-checking tools and an argument for each unsafe operation provide additional evidence. Correct length bookkeeping is necessary, but bounds, initialization, alignment and aliasing also matter.
The submission must compile, match the output cases and pass 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.