Kami Wan
Kami Wan

Reputation: 764

Why match object is borrowed in rust?

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

Answers (2)

Kevin Reid
Kevin Reid

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

Kushagra Gupta
Kushagra Gupta

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

Related Questions