Skip to content

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

Easy Primitives

Ownership through function calls

Pair labels with weights and return a header followed by one summary per pair. Repair summarise using the supplied helpers without changing their signatures or behavior.

pub fn summarise(labels: Vec<String>, weights: Vec<i64>) -> Vec<String>

Output format

The first string is "<label-count>x<weight-count>", using the original lengths of both input vectors. Include this header even when there are no pairs.

Pair inputs in their original order and stop at the shorter vector. For each pair, append "<label>=<score>|<char-count>|<weight>".

Helper Input and result
consume_label(label: String) -> usize Takes ownership of a label and counts its Unicode scalar values with chars().count().
score(weight: i64) -> i64 Returns weight * 2 + 1.
tag(label: String, score: i64) -> String Takes ownership of a label and formats "<label>=<score>".

Call all three helpers for each pair. Labels may be empty or contain Unicode, whitespace, or separator characters. Preserve their text exactly; do not trim or escape it. The character count is neither a byte count nor necessarily the number of visible characters: "é" has one scalar value, while "e\u{301}" has two.

Weights used in pairs are in -4611686018427387904..=4611686018427387903, so both the multiplication and addition in score fit in i64.

For example:

labels  = ["ada", "bo", "unused"]
weights = [3, 0]
result  = ["3x2", "ada=7|3|3", "bo=1|2|0"]

The header reports all three labels, but the unmatched label produces no pair string. An empty label vector with weights [1, 2] produces only ["0x2"].

Measure before consuming the vectors

labels.into_iter() takes ownership of the label vector. Passing weights directly to zip also consumes that vector through its IntoIterator implementation. Reading either vector's length afterward causes E0382.

Construct the header before the loop, while both vectors are available. Cloning entire vectors just to read their lengths would duplicate data that the header does not need.

Give each consuming helper a label

The starter moves label into tag, then tries to pass it to consume_label. That is another use-after-move error. Both helpers accept an owned String, and neither returns the input string to the caller.

A straightforward repair is to pass label.clone() to one helper and move the original into the other. This creates a separate owned string for the second call. Cloning is allowed in this exercise; avoid cloning either whole input vector.

The same issue does not arise for weight. i64 implements Copy, so passing it by value to score leaves the caller's variable usable. A by-value parameter moves a non-Copy value such as String, but copies a Copy value such as i64.

Here is a separate example of the distinction:

fn text_len(text: String) -> usize {
    text.chars().count()
}

fn doubled(number: i64) -> i64 {
    number * 2
}

fn main() {
    let text = String::from("hi");
    let length = text_len(text.clone());
    assert_eq!(length, 2);
    assert_eq!(text, "hi");

    let number = 3;
    assert_eq!(doubled(number), 6);
    assert_eq!(number, 3);
}

In APIs you design yourself, a helper that only reads text can often accept &str instead. That lets callers lend the text without transferring ownership or making a second owned string. The consuming signatures here are fixed so you can practice arranging calls around ownership.

A passing submission must compile, pass the tests, and pass clippy -D warnings. Leaving a supplied private helper unused can trigger dead_code, but that lint alone does not prove that every pair used every helper as requested.

Loading visualization…