I don’t know the exact page unfortunately since I am reading it in an epub.
I am having trouble understanding what this code does and why it is needed:
fn dead(&mut self, ctx: &mut BTerm) {
...
if let Some(key) = ctx.key {
match key {
VirtualKeyCode::P => self.restart(),
VirtualKeyCode::Q => ctx.quitting = true,
_ => {}
}
}
}
What exactly does the Some(key) bit do? Is it destructuring the Option<bracket_lib::prelude::VirtualKeyCode>
object?
Just switching on ctx.key obviously doesn’t work, but I don’t quite get why.
Hi!
ctx.key
is an Option
type - it has two possible values: None
(there is no data), or Some(x)
. In other languages, it’s often referred to as a Maybe
.
In this case, if no key is pressed then ctx.key
will equal None
. If a key is pressed, then it will equal Some(VirtualKeyCode::key)
.
There’s a few ways to get the contents of an Option
:
- You can
unwrap
it, which gives you the inner value - and crashes the program if there isn’t one. That’s probably not a good choice for reading keyboard input!
- You can query
my_option.is_some()
and then unwrap()
if it’s true. That’s not going to crash, but it can get pretty cumbersome.
- You can
match
against it: match my_option { None => ..., Some(x) => ... }
. That’s better, but in this case you’d wind up with a match
inside a match
—which is just confusing to the reader.
- There’s a bunch of
.unwrap_or
, map
and similar functionality that I didn’t want to touch so early in the book.
So Rust introduced if let
. It’s a hybrid of if
and match
. It can be broken into: if (pattern) matches (variable/expression)
. If the pattern matches, it runs the enclosed code. If it doesn’t match, it doesn’t (but like if
, you can use an else
if you wish). On top of that, it does match
style destructuring of the specified pattern. It’s invaluable, but a little tricky to get your head around.
So: if let Some(key) = ctx.key
takes the contents of ctx.key
, which is an Option
. If that matches Some(key)
then it destructures key
to be the contents of the Option
. Inside the if let
scope block you can then use key
like any other variable.
In other words, it’s the same as:
match ctx.key {
Some(key) => {
match key {
VirtualKeyCode::P => self.restart(),
//(etc)
}
}
None => {} // Do nothing
}
You’ll find if let
used a lot in Rust. It works with any pattern matchable expression. You could use if let VirtualKeyCode::P == key
to match on a single enum
entry. if let Ok(result) = my_dangerous_function_that_returns_a_result()
is a common way to extract the “it worked” path from functions that return errors - and so on.
Hope that helps!
Awesome thank you this is great.
So in the above case, if no key is pressed then the first if will evaluate to false. That implies that in this particular case the _ => {}
can never get hit?
If no key is pressed, then ctx.key
is None
- so the internal match
expression never evaluates at all.
If you were pressing a completely different key that isn’t in the match list, then ctx.key
is still Some
(it doesn’t know about the interior matching)—which is why you need the default, to ignore keys that you don’t care about.