Skip to content

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

Easy Primitives

Move values through a pipeline without cloning

Process names through a two-stage pipeline and return its event log. Each stage takes ownership of a value and returns it for the next stage.

pub fn pipeline(names: Vec<String>) -> Vec<String>

For each name, in input order, create a Tracked value, measure it, then mark it with the measured count as a decimal string. Keep every processed value alive until all names have been processed. Finally, drop the values in input order and return a snapshot of the log. Do not clone any Tracked value.

Empty input returns an empty log. Empty names, repeated names and Unicode names are valid. Preserve each name exactly. The measured count is its number of Unicode scalar values, as returned by chars().count(), not its byte count or number of displayed grapheme clusters.

Helpers supplied by the grader

These helpers are available without imports. Log is an Rc<RefCell<Vec<String>>>, and Tracked owns a name and a handle to that log.

Call Result and effect
new_log() Returns an empty Log.
Tracked::new(name, log) Takes a String and a Log, records new <name>, and returns a Tracked.
measure(t) Takes a Tracked, records measure <name>, and returns (t, count) with a usize count.
mark(t, note) Takes a Tracked and a String, records mark <name> <note>, and returns the value.
drop(t) Consumes the value and runs its destructor, recording drop <name>.
snapshot(log) Takes a Log handle and returns a cloned Vec<String> of its current entries.

Tracked also has a custom Clone implementation. It records clone <name>, clones the name string and shares the same log. Cloning a Log handle itself does not produce a clone event: that operation clones an Rc, not a Tracked. Use a cloned log handle when constructing each value.

For names ["ada", "bo"], return these eight strings:

new ada
measure ada
mark ada 3
new bo
measure bo
mark bo 2
drop ada
drop bo

The spaces in each entry are literal. An empty name produces new , measure , mark 0 and drop , including the trailing or doubled spaces.

Keep the value returned by each stage

The starter passes t to measure, discards the returned value, and then tries to use the old binding. That produces E0382: use of a moved value. Capture both parts of the result with let (t, n) = measure(t); and pass the returned t to mark.

This pattern also works without the logging helpers:

fn measure_name(name: String) -> (String, usize) {
    let count = name.chars().count();
    (name, count)
}

fn main() {
    let name = String::from("héllo");
    let (name, count) = measure_name(name);
    assert_eq!(name, "héllo");
    assert_eq!(count, 5);
}

Moving the value through the calls avoids duplicating its name buffer. The exercise as a whole still allocates for log entries, notes, the vector of retained values and the final snapshot.

Make destruction order explicit

A Vec owns its elements, but its automatic element-drop order is not a documented guarantee. Since this task requires an exact event order, consume the retained values in input order and call drop on each one before taking the snapshot. for t in alive { drop(t); } does that explicitly.

Taking the snapshot too early omits the drop events. Dropping each value immediately after marking it interleaves the drop events with later names. Both produce a different log from the requested pipeline.

What cloning means depends on the type

Clone is implemented by each type. It does not promise a deep copy, a fixed allocation count or a particular runtime cost.

Type What its clone does
i64, bool, char Copies a scalar value. Assignment already copies these types.
Shared reference &T Copies the reference, retaining access to the same value.
String Duplicates the string contents into independently owned storage; copying a nonempty string requires work proportional to its bytes.
Vec<T> Clones its elements; their Clone implementations determine whether they copy data, share it or do other work.
Rc<T> / Arc<T> Creates another owning handle to the same allocation, updating the reference count without cloning T.
HashMap<K, V> Clones keys and values, along with its hasher; cost depends on their implementations and the map's storage.

Empty buffers and zero-sized elements make fixed allocation counts misleading. A Vec<String> duplicates its strings, while a Vec<Rc<String>> clones handles to shared strings. Cloning every separate name once costs work proportional to their total size; cloning the entire collection on every iteration can multiply that work by the number of iterations.

In this task, cloning the log handle is intentional sharing. Cloning a Tracked introduces an extra event and an extra value to destroy, so it cannot replace using the value that measure already returns.