Skip to content

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

Medium Primitives

Ownership inside loops

Repair two functions that use a vector in different ways. One consumes its elements to build running labels; the other reads the elements and returns the original vector.

pub fn fold_labels(prefix: String, items: Vec<String>) -> Vec<String>
pub fn survey(items: Vec<String>) -> (usize, Vec<String>)

Build the running labels

fold_labels returns the initial prefix followed by one accumulated label per item, in input order. Keep the supplied helper and its signature:

fn join_owned(a: String, b: String) -> String {
    format!("{a}/{b}")
}

For prefix "p" and items ["a", "b", "c"], return:

["p", "p/a", "p/a/b", "p/a/b/c"]

No items means the result contains only the prefix. Every join adds exactly one slash, even when either string is empty or already contains slashes. For example, prefix "" and items ["x", ""] produce ["", "/x", "/x/"]. Preserve Unicode and whitespace as ordinary text; these are labels, not normalized filesystem paths.

Count without consuming the vector

survey returns the number of nonempty strings and the original items, unchanged and in the same order. Only "" is empty: a string containing spaces still counts.

["a", "", "b"] -> (2, ["a", "", "b"])
["", "", ""]  -> (0, ["", "", ""])
[]            -> (0, [])

Use a borrowing pass for this part rather than rebuilding or cloning the input vector.

Choose how the loop receives elements

For a Vec<T>, these loop forms have different ownership behavior:

Loop Item type Effect on the vector
for item in items T Consumes the vector
for item in &items &T Borrows elements for reading
for item in &mut items &mut T Borrows elements for mutation

A for loop uses IntoIterator. For an owned vector, that transfers ownership into the iterator. Borrowed iteration leaves the vector available after the borrow ends.

fold_labels can consume items, since each string is passed by value to join_owned. survey needs to return its input, so shared iteration is a straightforward fit.

Carry ownership into the next iteration

The starter moves prefix into out, then attempts to pass it to join_owned. That reuse causes E0382. The initial snapshot and the continuing accumulator need separate owned strings.

Keep an accumulator and assign the helper's returned string back to it:

fn join_owned(a: String, b: String) -> String {
    format!("{a}/{b}")
}

fn main() {
    let mut acc = String::from("p");
    for item in [String::from("a"), String::from("b")] {
        acc = join_owned(acc, item);
    }
    assert_eq!(acc, "p/a/b");
}

Each iteration consumes the old accumulator and installs its replacement. Without that replacement, a later iteration could try to use a moved value.

The exercise also retains earlier prefixes. One clear solution clones the accumulator when adding each snapshot to the output. That is an explicit cost of this implementation, not a claim that this exact clone placement or allocation count is required for every solution.

Repeatedly calling pop() would visit the input from the end. Preserve the requested order when choosing an iteration strategy.

Borrow indexed elements instead of moving them

The starter's let s: String = items[i]; attempts to move a non-Copy string out through vector indexing. Rust rejects that with E0507 because indexing does not provide an ownership-transfer operation.

items.iter() yields references instead. Its is_empty() checks can count nonempty strings without removing or copying them. Once the counting expression finishes, return the vector alongside the count.

Indexing is not inherently wrong: &items[i] would borrow the element. An iterator simply avoids manual index bookkeeping for this whole-vector pass.

A passing submission must compile, pass the tests, and pass clippy -D warnings.

Loading visualization…