rustrust-rustlings

Rustlings Quiz2: Why is i of type &usize instead of usize


I am new to Rust and I am attempting the Rustlings quiz2 problem.

Below is the whole code for reference.


// quiz2.rs
// This is a quiz for the following sections:
// - Strings
// - Vecs
// - Move semantics
// - Modules
// - Enums

// Let's build a little machine in the form of a function.
// As input, we're going to give a list of strings and commands. These commands
// determine what action is going to be applied to the string. It can either be:
// - Uppercase the string
// - Trim the string
// - Append "bar" to the string a specified amount of times
// The exact form of this will be:
// - The input is going to be a Vector of a 2-length tuple,
//   the first element is the string, the second one is the command.
// - The output element is going to be a Vector of strings.
// No hints this time!

// I AM NOT DONE

pub enum Command {
    Uppercase,
    Trim,
    Append(usize),
}

mod my_module {
    use super::Command;

    // TODO: Complete the function signature!
    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!
            let s = match command {
                Command::Uppercase => string.to_uppercase(),
                Command::Trim => string.trim().into(),
                Command::Append(i: &usize) => format!("{}{}", string.to_owned(), "bar".repeat(*i))
                };
            output.push(s)
            }
        output
    }
}

#[cfg(test)]
mod tests {
    // TODO: What do we need to import to have `transformer` in scope?
    use crate::my_module::transformer;
    use super::Command;

    #[test]
    fn it_works() {
        let output = transformer(vec![
            ("hello".into(), Command::Uppercase),
            (" all roads lead to rome! ".into(), Command::Trim),
            ("foo".into(), Command::Append(1)),
            ("bar".into(), Command::Append(5)),
        ]);
        assert_eq!(output[0], "HELLO");
        assert_eq!(output[1], "all roads lead to rome!");
        assert_eq!(output[2], "foobar");
        assert_eq!(output[3], "barbarbarbarbarbar");
    }
}

In particular, I am not sure why in the transformer function, the i in Command::Append(i) is of type &usize instead of usize as defined in the Command enum of Append(usize).

Specifically I am referring to this line in the solution of the quiz:

Command::Append(i: &usize) => format!("{}{}", string.to_owned(), "bar".repeat(*i))

I asked chatgpt and this was the reason but I am not sure if it is right:

The reason that command is a reference is because input is a vector of tuples containing a String and a Command enum. The String is owned and can be moved out of the tuple, but the Command enum is a non-copyable type, so we have to pass it by reference to avoid moving it out of the tuple. This is why command is a reference and not an owned value.

and because the command variable is a reference...

In the transformer function, the command variable is a reference to a Command enum value, so when you pattern match on it and match to the Command::Append(i) case, i will be a reference to the usize value stored in the Append variant.


Solution

  • This is due to what's commonly referred to as match ergonomics if you match a like this

    match &Foo(99) {
        Foo(x) => {},
    }
    

    the compiler will implicitly dereference to a Foo and then match with a ref pattern:

    match *&Foo(99) {
        Foo(ref x) => {},
    }
    

    Note: the compiler will not actually try to move Foo or insert a dereference there, it's just an illustration what happens, the reference is dereferenced and a new reference is created to the contained value. which makes x a reference to the original type contained in Foo.

    That happens twice in your code:

    for (string, command) in input.iter() {/*..*/}
    

    matches each element of input.iter() which are of type &(String, Command) with the pattern (string, command) so command will be of type &Command and the same happens again in your match:

    match command {
        //..
        Command::Append(i) => {/*..*/}
    }
    

    gets implicitly converted to something like

    match *command {
        //..
        Command::Append(ref i) => {/*..*/}
    }
    

    and that is why the type of i in your match is &usize