Reputation: 764
The code case is from rustlings 'quiz2.rs'. I known command in ‘for(string,command)’ is borrowed from vector iterator. The command is borrowed, but why the 'n' Append(n) is also borrowed?
pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
// TODO: Complete the output declaration!
let mut output: Vec<String> = vec![];
for (string, command) in input.iter() {
// TODO: Complete the function body. You can do it!
match command {
Command::Uppercase => output.push(string.to_uppercase()),
Command::Trim => output.push(string.trim().to_string()),
Command::Append(n) => {
let can_mv_str = string.to_string() + &"bar".repeat(*n);
output.push(can_mv_str);
}
}
}
output
}
Upvotes: 1
Views: 221
Reputation: 43962
First, Vec::iter()
returns an iterator over references to the elements of the vector, that is, &(String, Command)
.
Then, whenever you write a pattern for some specific structure, like the 2-element tuple (string, command)
in the for
loop, but the input is a reference to that structure, Rust matches "through" the reference and automatically gives you references to the elements (because it's not possible in general to not get references, since not every type is Copy
).
So, the type of command
is &Command
. Then the same thing happens to the match command {
and every variable binding in the match
's pattern (that is, n
) will be a reference too.
If you want to avoid this, what you have to do is explicitly write out matching against the references:
match command {
...
&Command::Append(n) => {
let can_mv_str = string.to_string() + &"bar".repeat(n);
output.push(can_mv_str);
}
}
Or, you can dereference the input to the match
(this won't necessarily try to move out of the reference — as long as you don't bind any non-Copy
values):
match *command {
...
Command::Append(n) => {
let can_mv_str = string.to_string() + &"bar".repeat(n);
output.push(can_mv_str);
}
}
Finally, if you want to completely avoid the magic and write out a program that is doing the whole thing explicitly, you also need to adjust the for
pattern:
for &(ref string, ref command) in input.iter() {
The ref
means "please don't try to move this value out of what I'm matching; just give me a reference to it. It's rarely seen in modern Rust because the automatic matching behavior I'm talking about makes it mostly unnecessary. This feature of Rust is called “match ergonomics”, because it saves you from writing a lot of &
and ref
all the time. But, as you've seen, it can lead to surprising behavior, and the old explicit style also lets you avoid dealing with needless references to Copy
types like integers.
If you'd like to try writing Rust without ever using match ergonomics, to understand “what's really going on”, you can enable a Clippy restriction lint to mark any places where the pattern doesn't actually fit the type of what it's matching:
#[warn(clippy::pattern_type_mismatch)]
and run cargo clippy
to see the new warnings. You'll probably find that there's quite a lot of patterns that are implicitly working on references!
Upvotes: 3
Reputation: 614
This is because of RFC 2005. Before that, there used to be special syntax of ref
and ref mut
when you wanted to take a reference to an inner field in match.
What happens here is that your command
is an &Command
. You match on it but the arms are all Command
. So command
gets de-referenced automatically. But then, in Command::Append
you take ownership of the inner n
. This cannot happen as your command
was a reference. So rust gives you a reference to n
.
You can fix this in multiple ways. The easiest is to dereference the n
yourself, like so:
match command {
Command::Append(&n) => ..., // n is owned here
...
}
This works when n
is Copy
. You can also do a let n = n.clone()
in the match body itself if n
is not Copy
but is Clone
.
You can also take ownership of command
, like so:
match *command { // if Command is Copy
Command::Append(n) => ..., // n is owned here
...
}
// OR
match command.clone() { // if Command is Clone
Command::Append(n) => ..., // n is owned here
...
}
If you cannot do any of the above, then you will need to work with the reference itself or maybe change your iterator from input.iter()
to input.into_iter()
to get an owned Command
from the start.
Upvotes: 2