Skip to content

← Ownership I: Moves, Copy, Clone, Drop step 12 of 20

Easy Primitives

Schedule destruction with a by-value helper

Repair the starter so it destroys each Tracked value at the required time. Use the supplied generic helper with an empty body:

fn my_drop<T>(_value: T) {}
pub fn schedule(names: Vec<String>, drop_after: Vec<usize>) -> Vec<String>

Required event order

Visit names in order. For each zero-based index, construct exactly one Tracked value. Construction logs "new " followed by the unchanged name.

If that index appears in drop_after, pass the new value to my_drop immediately, before constructing the next value. Otherwise, retain it. After all names have been visited, pass the retained values to my_drop one at a time in reverse creation order. Each destruction logs "drop " followed by its name. Return the complete log after every value has been released.

For names ["a", "b", "c"] and drop_after [1], return:

["new a", "new b", "drop b", "new c", "drop c", "drop a"]

Duplicate indices select the same value once. Out-of-range indices select nothing, and their order does not change the schedule. Indices are nonnegative and fit usize. Empty names input produces an empty log. Names may be empty, duplicated or contain arbitrary Unicode and control characters; preserve them exactly. An empty name produces the strings "new " and "drop ", including their trailing spaces. Equal names still represent distinct values.

Supplied helpers

The harness supplies these definitions; do not redefine them:

Item Purpose
Log A shared handle to the event vector.
new_log() -> Log Creates an empty log.
Tracked::new(name: String, log: Log) -> Tracked Owns the name and logs construction.
snapshot(log: Log) -> Vec<String> Copies the accumulated events.

Tracked does not implement Clone. Cloning a Log handle shares the same log; it does not duplicate a Tracked value. Vec::pop removes the newest retained value, so a pop loop gives explicit control over the required release order. Do not rely on automatic Vec element destruction to provide that order.

Why an empty function can destroy a value

The standard library's drop function also takes its argument by value and has an empty body. Its parameter is dropped before the function returns. This uses the language's ordinary destruction rules. See the drop documentation.

Here is a complete example:

fn my_drop<T>(_value: T) {}

struct Notice(&'static str);

impl Drop for Notice {
    fn drop(&mut self) {
        println!("drop {}", self.0);
    }
}

fn main() {
    let notice = Notice("first");
    my_drop(notice);
    println!("after");
}

It prints drop first before after. Notice is moved into my_drop and cannot be used afterward. For a Copy argument, the call instead receives a copy, leaving the original usable. Passing a reference does not destroy the referenced value.

Drop is not a requirement for cleanup

A Drop implementation supplies custom destructor code. A type without its own Drop implementation can still own fields that need cleanup. String is an example: it has no direct Drop implementation, but its owned storage is released through its fields. Vec does implement Drop. Neither case calls for a T: Drop bound on this generic helper.

Such a bound would reject String and i32, while accepting Tracked and Vec. It is not a general test for whether a value needs destruction. Keep fn my_drop<T>(_value: T) {}; T has the usual implicit Sized bound, and no Drop bound is needed. Rustc's drop_bounds warning flags this misleading use of the trait as a generic constraint.

Use my_drop(value), not value.drop()

The starter calls t.drop() directly, producing E0040. Explicit calls to the Drop trait's destructor method are forbidden. That method borrows &mut self; calling it as an ordinary method would leave the value usable after cleanup and eligible for later cleanup again. Depending on the type, that could cause resource misuse. Not every destructor frees memory.

Passing this non-Copy Tracked value to my_drop transfers ownership instead. Normal scope cleanup runs its destructor exactly once here. Replace the direct method call and preserve the explicit newest-first survivor loop.

Passing requires compilation, the exact event sequence and Clippy with warnings treated as errors.