Reputation: 1139
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 String
s—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
Reputation: 361585
My primary question here is, why does Rust require me to borrow with
&self.p1_icon
within thematch
call, but not when I callprintln
?
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 saidString
, 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