Better Programming

Advice for programmers.

Follow publication

Enums and Pattern Matching in Rust

Rust basics

Abhishek Gupta
Better Programming
Published in
5 min readFeb 29, 2020

--

Photo by Patrick Tomasso on Unsplash

The code for this article 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 its 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 let’s look at a type that 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 the PlayerAccountType type with a struct, but you don’t have 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 methods
  • Option<T> and Result<T,E> are widely used enums 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:

No surprises here! But you cannot write this using match.

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: As per this comment, if-else-if is possible using Guards. Thanks, @rpring9!

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 its corresponding expression to 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.

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

The 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 Optional in Java.

pub enum Option<T> {
Some(T),
None,
}

Don’t worry about the T symbol. Just understand that it is a generic type parameter to allow the enum to work with various types rather than tying it to a specific one, e.g., a String.

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 users (it’s optional! Duh!).

Here is the corresponding function:

The 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 don’t 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-match, and use cargo run. To pass an argument, you can use cargo 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 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 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 it 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.

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! :(");
}

Conclusion

Don’t forget to check out these resources:

That’s all for this quick tour of enums and how you can use pattern matching with them. Stay tuned for more!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Abhishek Gupta
Abhishek Gupta

Written by Abhishek Gupta

Principal Product Manager at Microsoft | I ❤️ Databases, Go, Kubernetes

Responses (1)