Skip to content

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

Easy Primitives

Move semantics: the relay

Repair relay so it consumes station names from the back of a vector and joins them into one route:

pub fn relay(mut stations: Vec<String>) -> String

Separate adjacent names with exactly " -> ", including the spaces. Preserve each name unchanged, including empty names, whitespace and any separator text already inside a name.

Stations Result
[] ""
["depot"] "depot"
["a", "b", "c"] "c -> b -> a"
["", "north"] "north -> "
["x -> y", "z"] "z -> x -> y"

Keep the supplied link helper and its signature. Use its returned string as the route for the next iteration. The starter already handles empty input and removes stations in the required order.

Follow ownership through the helper

fn link(head: String, tail: String) -> String {
    format!("{head} -> {tail}")
}

link takes ownership of both input strings and returns a newly formatted string. After link(route, stop), the old value of route has been moved. The caller needs to store the result before it can use route again.

Moving a String transfers ownership of its buffer; it does not clone the text. The compiler may optimize away physical transfers, so a move is not a promise that particular bytes are copied to a new address. Types that implement Copy, such as integers, can instead be copied when passed by value. String does not implement Copy.

Reinitialize the route on every iteration

The starter discards the helper's output:

let _ = link(route, stop);

This leaves route unavailable both for another iteration and for the final return. Rust rejects the function with E0382 even if a particular input would skip the loop. The function must be valid for all its inputs.

Store the returned value in the same binding:

route = link(route, stop);

The right-hand side produces the new string before the assignment writes it into route. Add mut to the binding created by the first pop so reassignment is allowed. Each completed iteration then leaves a valid route for the next call or the final return.

Check your understanding Would changing the starter to let _ = link(route.clone(), stop); fix the task? Show answer

It avoids moving the original route, but it still discards the newly built string. The result would remain the last station instead of the full route. For ["a", "b", "c"], it would return "c".

Assigning the result while cloning would produce the right output, but the clone is unnecessary: the old route is no longer needed after each call. Pass it by value and keep the result.

Why pop and let-else fit this loop

stations.pop() removes the last element and returns it as Some(String), or returns None when the vector is empty. Moving the first popped string directly into route avoids a leading separator:

let Some(mut route) = stations.pop() else {
    return String::new();
};

The else branch must diverge; here it returns from the function. On the other branch, route owns the first popped station. The loop combines each remaining station with that accumulated route.

mut stations permits changes to the function's local vector binding. It does not require the caller's binding to be declared mut; the caller transfers the vector into this function.

This exercise demonstrates consuming and reinitializing a value. Repeated formatting rebuilds the accumulated prefix, so this particular helper is not a recommendation for efficiently joining a large collection of strings.

Submissions must compile, pass the cases and pass the Rust Clippy gate.