I have started learning the Rust language, to see what people find attractive about it, and to understand cases where I may want to use it. A post on correctness with types by Amos (fasterthanlime) seemed to offer some tantalising benefits. It reminded me of some of the struggles I had with Go when I first started learning it years ago -- things like my inability to control exactly how my types are used to prevent programmers from making mistakes, and my inabilty to create types that could do certain things that the core language types can do (e.g., telling Go how to handle len or range with my own structs). Once I learned these things were not possible in Go, I proceeded with my learning and still used the language productively, ignoring or working around these defecits.
Anyway, this post is not about my reasons for learning Rust, but instead to point out something interesting I found that I can't find in the documentation yet. One of the promises in Amos' post is about how Rust should enable me to write correct code, to avoid common errors. One thing that seems promising is that Rust is clever enough to tell me when my match
doesn't cover all possible conditions. For example, consider the following code:
fn main() {
let number: u8 = 4;
match number {
0 => println!("Zero"),
}
}
The compiler will throw an error, because I've missed cases -- for example, when number == 4
:
error[E0004]: non-exhaustive patterns: `1_u8..=u8::MAX` not covered
--> src/main.rs:4:11
|
4 | match number {
| ^^^^^^ pattern `1_u8..=u8::MAX` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or mor
e match arms
= note: the matched value is of type `u8`
error: aborting due to previous error
It even lets you know which patterns haven't been covered. We can fix this by adding a new condition that covers the remaining possible cases:
fn main() {
let number: u8 = 4;
match number {
0 => println!("Zero"),
1..=u8::MAX => println!("Greater than zero"),
}
}
And then it compiles without issue. I thought this is very nice to have, forcing us to consider all possible cases, and reminding us when we miss them (and when we miss them). As The Book says:
Rust knows that we didn’t cover every possible case and even knows which pattern we forgot!
...
Rust also has a pattern we can use when we don’t want to list all possible values.
(emphasis added)
I wanted to know what happens if I tried my own custom expressions using match guards, because that would be very impressive if the compiler could evaluate those for missing conditions:
fn main() {
let number: u8 = 4;
match number {
i if i == 0 => println!("Zero"),
i if i > 0 => println!("Greater than zero"),
}
}
Completely understandable, this code does not compile, even though it covers the same cases as the original:
error[E0004]: non-exhaustive patterns: `_` not covered
--> src/main.rs:4:11
|
4 | match number {
| ^^^^^^ pattern `_` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or mor
e match arms
= note: the matched value is of type `u8`
It turns out in fact that the compiler does not look into arbitrary expressions like these to determine whether the cases included are exhaustive or not. Not surprising, but I haven't been able to find this documented anywhere yet, which led me to checking for myself. The solution in this case is to simply include a case for the _
pattern when using match guards:
fn main() {
let number: u8 = 4;
match number {
i if i == 0 => println!("Zero"),
i if i > 0 => println!("Greater than zero"),
_ => println!("Fell through"), // This should not be possible to reach
}
}
Top comments (0)