This blog covers concepts related to the following Rust topics:
Enum- Pattern matching
the code is available on GitHub
enum
An enum is similar to struct in the sense that it is a type for which you can have multiple variants (same as that of a class and it's an instance). But the variants of an enum are fixed and defined within the enum itself.
A Player might be represented by a struct:
struct Player {
name: String,
rank: i32,
}
You can instantiate multiple instances of a Player. But lets look at a type which is a little more niche
enum PlayerAccountType {
Free,
Paid,
}
Imagine you have only two account types - a free and a paid one. You can use an enum to represent this concept. And, since it just another type, you can use it in the Player struct
struct Player {
name: String,
rank: i32,
acc_type: PlayerAccountType,
}
you can represent
PlayerAccountTypetype with astruct, but don't need to since its possible variants are already known
You can reference enum variants using ::. For e.g. to instantiate a Player who has a paid account:
let paid_member = Player{acc_type: PlayerAccountType::Paid, name: String::from("john"), email: String::from("john@doe.com")};
It's also possible to add data to enum variants. For e.g.
enum foo {
foo1(String),
foo2(String),
}
Note:
- In addition to data,
enums can also have methodsOption<T>andResult<T,E>are widely usedenums and are part of the Rust standard library
Pattern matching
Rust provides the match keyword which behaves the same way as a switch statement would (Rust does not have switch). Before exploring match, let's see a simple if-else-if example:
let num = 101;
if num < 100 {
println!("{} is less than 100", num)
} else if num > 100 {
println!("{} is more than 100", num)
} else if num == 100 {
println!("{} is equal 100", num)
}
No surprises here! But you cannot write this using match
match num {
num < 100 => {
println!("{} is less than 100", num);
}
num < 100 => {
println!("{} is more than 100", num);
}
num == 100 => {
println!("{} is equal to 100", num);
}
}
This is not possible because the match operator needs a value against which it can execute the match process. It does not execute a given expression for you.
Update: looks like
Guardscan help with the aboveif-else-ifscenario. Thanks to Richard for pointing this out!
The general format is as follows:
- you have a value you want to match against and a bunch of possible options, also known as a
match arm - each arm is a combination of the pattern to match on and it's corresponding
expressionto be run if the match is successful
All this sounds abstract, so let's look at an example of matching with an enum
Match enum
Let's start by defining an enum:
enum Choice {
One,
Two,
Three,
}
.. and match against Choices
//suppose this was passed by the user and you stored in a variable
let choice = Choice::One;
match choice {
Choice::One => println!("Option 1"),
Choice::Two => println!("Option 2"),
Choice::Three => println!("Option 3"),
}
Here, the variable choice is the value being matched followed by three match arms. For each arm, there is a pattern e.g. Choice::One and the corresponding expression e.g. println!("Option 1") separated by a =>
In this case, the output will be
Option 1
Match an Option
Rust standard library provides the Option enum whose purpose is to define a type to represent a scenario where you may or may not have a value. This makes the code obvious and is a better choice than using null, nil or similar options to denote the absence of a value
This is similar to
OptionalinJava
pub enum Option<T> {
Some(T),
None,
}
Don't worry about the
Tsymbol. Just understand that it is agenerictype parameter to allow theenumto work with various types rather than tying it to a specific one e.g. aString
Let's understand this with an example. This is a simple CLI program that accepts an argument from the user (the name) and uses it to display a greeting. If the user does not pass anything, it uses a default greeting. This is a reasonable example of how to use Option since we may or may not have an argument passed in by the uses (it's optional! duh!)
Here is ther corresponding function:
fn parse_name_arg() -> Option<String> {
let args: Vec<String> = std::env::args().collect();
if args.len() == 1 {
None
} else {
let name = &args[1];
Some(String::from(name))
}
}
parse_name_arg function converts passed in arguments into a vector (Vec<String>) and returns an Option. If an argument was not passed, it returns None or Some (with the value) - both are variants of an Option. Now we can use this function as such:
let name = parse_name_arg();
match name {
Some(n) => println!("Hello {}!", n),
None => println!("Hello there!"),
}
We store the result of parse_name_arg in a variable called name (which is an Option), match different possibilities and execute the greeting accordingly. If you were to pass john as an argument, you will get back Hello john!. If you dont pass an argument, you will get back a generic greeting Hello there!
To try this out, just clone the GitHub repo, change to the correct directory i.e.
cd learning-rust/enums-matchand usecargo run. To pass an argument, you can usecargo run <your argument
The value of an enum is available in the match clause (also called arm). This is why we were able to extract the passed in an argument using Some(n) => println!("Hello {}!", n),
Using let with match
How about storing the greeting in a variable? You can use let with match to return a value rather as well. Let's tweak the program a little bit
let greeting = match name {
Some(n) => n,
None => String::from("there"),
};
println!("Hello {}!", greeting)
We stored the result of the match in a variable called greeting and used with the println! macro which makes the program much simpler!
Exhaust your choices!
By default, match requires you to fulfill or account for all the possible options. Let's look at an example. Here is yet another enum whose value we will match against
enum Days {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
and this is how we want to match (in this case, we happen to be interested in Friday only)
let today = Days::Friday;
match today {
Days::Friday => println!("thank god its Friday!"),
}
But this will not compile. You will see an error as such:
error[E0004]: non-exhaustive patterns: `Monday`, `Tuesday`, `Wednesday` and 3 more not covered
To get around this problem, we can use the _ placeholder:
match today {
Days::Friday => println!("thank god its Friday!"),
_ => (),
}
This simply ignores the other possibilities. In such situations, you can also use if let to keep it concise
let today = Days::Monday;
if let today = Days::Monday {
println!("its Monday already! :(");
}
Don't forget to check out these resources:
- The Rust Programming Language book
- Rust standard library documentation
- Rust by Example
That's all for a quick tour of enums and how you can use pattern matching with them. Stay tuned for more!
Oldest comments (2)
Awesome
glad you liked it !