We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 3 of 20
Move semantics: the relay
Consume a Vec<String> from the back and produce one route string.
pub fn relay(mut stations: Vec<String>) -> String
["a", "b", "c"] becomes "c -> b -> a" — last station first. An empty vector
becomes "". A single station becomes itself, with no arrow.
The one-sentence summary of Rust
Assignment moves.
Every error in this track is a corollary of that sentence, so it is worth being precise about what it covers. All four of these are moves:
let route = other_route; // let-binding
route = other_route; // assignment
takes_ownership(other_route); // passing by value
return other_route; // returning by value
They are the same operation: the value’s bytes are copied to a new location and the old name stops being usable. Rust does not distinguish “passing an argument” from “assigning to a variable” here, because at the level ownership cares about they are the same event — a value changed owner.
The corollary that trips people up is that a loop body runs more than once. A move inside a loop is fine on the first iteration and a disaster on the second, and rustc says exactly that:
error[E0382]: use of moved value: `route`
|
| while let Some(stop) = stations.pop() {
| ------------------------------------- inside of this loop
| let _ = link(route, stop);
| ^^^^^ value moved here, in previous iteration of loop
“in previous iteration of loop” is the compiler telling you it checked your code at every program point, not just line by line. It is not guessing.
The fixed helper
fn link(head: String, tail: String) -> String {
format!("{head} -> {tail}")
}
You may not change this signature. It eats both strings and produces a new one. That is the shape of an enormous amount of real Rust: a function that consumes its inputs and hands you a result. The question every such call forces on you is “do I still need what I just gave away?” — and if the answer is yes, you have exactly three outs, the same three as the previous problem.
::: question The starter’s loop is let _ = link(route, stop); and it fails with E0382. Which of the three outs actually applies?
Have the function hand it back — except that here it already does. link
returns a String; the starter throws it away with let _ =. Catch it:
route = link(route, stop);
Now route is moved out on every iteration and refilled on every iteration,
so the next turn has something to give. This “move it in, get it back, put it
in the same slot” pattern is the ownership shape of every fold, every builder
chain, and every state machine written in safe Rust.
Cloning would also compile — link(route.clone(), stop) — and it would be
wrong: the result is discarded, so the loop would build nothing and you would
pay for an allocation per station to achieve it. Worth noticing that the
compiler cannot tell you this. clippy::redundant_clone is a nursery lint,
off by default, so a gratuitous clone here passes -D warnings silently. The
tests are what catch it.
:::
Notes on the shape
stations.pop() removes and returns the last element as an
Option<String>, which is why the output comes out reversed. The vector is
declared mut in the signature for exactly this reason — pop needs to modify
it. mut on a parameter is a statement about the local binding inside the
function; it says nothing to the caller, who gave the vector away anyway.
The empty case is worth handling before the loop, and let ... else does it
without a nested if:
let Some(mut route) = stations.pop() else {
return String::new();
};
Read that as: bind the Some payload to route, or run the else block, which
must diverge. It is the idiomatic Rust for “get me the value or get me out of
here”, and clippy’s manual_let_else will nudge you towards it once pedantic
lints switch on later in the course.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.