FrenchMajesty
FrenchMajesty

Reputation: 1139

Why am I required to borrow while using a `match`?

I am struggling to understand the principles behind borrowing while creating a simple program. This is what I have

pub struct GameBoard {
    p1_icon: String,
    p2_icon: String,
    current_player: i8,
}

impl GameBoard {
    pub fn prompt_turn(&self) {
        let misc = String::from("?");
        let icon = match self.current_player {
            1 => &self.p1_icon,
            2 => &self.p2_icon,
            _ => &misc,
        };
        println!(
            "It is Player {}'s turn to go. Please pick your next move to drop a {}",
            self.current_player, icon
        );
        println!("This is a test: {}", self.p1_icon)
    }
}

My primary question here is, why does Rust require me to borrow with &self.p1_icon within the match call, but not when I call println?

My next question is, I am having challenges working with string literals in Rust. In my case, there are some strings that are meant to be dynamic—thus why I use Strings—but there are others that are just hard-coded (see misc).

It feels a bit overkill to create a String, then borrow from said String, so I can satisfy the constraints that "match values must all be &String". Is this really how Rust expects developers to code this way, or am I missing something?

Upvotes: 1

Views: 130

Answers (1)

John Kugelman
John Kugelman

Reputation: 361585

My primary question here is, why does Rust require me to borrow with &self.p1_icon within the match call, but not when I call println?

println! is a macro, not a regular function call, which allows it to bend the rules. When println! is expanded it inserts hidden & operators to borrow its arguments.

Running cargo expand to expand macros shows that the two println!s expand to:

{
    ::std::io::_print(
        ::core::fmt::Arguments::new_v1(
            &[
                "It is Player ",
                "\'s turn to go. Please pick your next move to drop a ",
                "\n",
            ],
            &[
                ::core::fmt::ArgumentV1::new_display(&self.current_player),
//                                                   ^-- added borrow
                ::core::fmt::ArgumentV1::new_display(&icon),
//                                                   ^-- added borrow
            ],
        ),
    );
};
{
    ::std::io::_print(
        ::core::fmt::Arguments::new_v1(
            &["This is a test: ", "\n"],
            &[::core::fmt::ArgumentV1::new_display(&self.p1_icon)],
//                                                 ^-- added borrow
        ),
    );
}

Notice how &self.current_player, &icon, and &self.p1_icon are all automatically borrowed.

I don't know if I like how println! does this. It makes it easier to type print statements, but in the process it muddles the borrowing story for new Rustaceans. Rust usually tries to avoid such surprises.


It feels a bit overkill to create a String, then borrow from said String, so I can satisfy the constraints that "match values must all be &String". Is this really how Rust expects developers to code this way, or am I missing something?

You're right, it's overkill. There's no need for misc to be a heap-allocated String. You can make it a plain &str and it'll compile fine.

let misc = "?";
let icon = match self.current_player {
    1 => &self.p1_icon,
    2 => &self.p2_icon,
    _ => misc,
};

If you're wondering why this compiles when the match arms have different types (&String and &str), it's thanks to type coercion (warning: advanced read).

Rust tries to find a common type for the match arms. Using its least upper bound coercion process, the compiler figures out that the result of the match block should be &str, and that it should coerce the two &String arms into &str. Such coercion is possible because of the general rule that coercion is allowed from "&T or &mut T to &U if T implements Deref<Target = U>", which applies since String implements Deref<Target = str>.

In other words, it automatically inserts dereferences as if the match block had been written:

let misc = "?";
let icon = match self.current_player {
    1 => &*self.p1_icon,
    2 => &*self.p2_icon,
    _ => misc,
};

Upvotes: 6

Related Questions