This is the first article in about 3 or 4 articles that will discuss enum and pattern matching. In this one, I'll show you how to define and use a basic enum.
β οΈ Remember!
You can find all the code snippets for this series in its accompanying repo
If you don't want to install Rust locally, you can play with all the code of this series in the official Rust Playground that can be found on its official page.β οΈβ οΈ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.
β I try to publish a new article every week (maybe more if the Rust gods π are generous π) so stay tuned π. I'll be posting "new articles updates" on my LinkedIn and Twitter.
Table of Contents:
What's an enum:
In the Struct article, we saw how a Struct can be used to group relevant data together as in the width and height of a Rectangle. The enum is similar to the struct in a way that it "enumerates" all the "variants" of the custom type we are creating in one place. Think of our earlier Rectangle struct, with enum, we can make a Shape type that contains a Rectangle, a Circle, and a Square. Each with its own "properties" and "functionality". To me, I couldn't find a similar concept in Python so this is a new one for me.
Defining enum:
We use the enum
keyword to define enums. The definition can be as simple as the following:
enum SimpleAnswer {
True,
False,
}
Here we are defining a SimpleAnswer custom type and its variants could be either True or False (needed for an automatic exam grading system for example).
We can very easily use them and pass them to functions as follows:
enum SimpleAnswer {
True,
False,
}
fn main() {
// Enum values
let correct_answer = SimpleAnswer::True;
let wrong_answer = SimpleAnswer::False;
// Enums can be passed to functions
evaluate_answer(correct_answer);
evaluate_answer(wrong_answer);
}
fn evaluate_answer(user_answer: SimpleAnswer) {}
Here we are creating two answers that use our SimpleAnswer type, one is correct and the other is wrong. And we can pass any of them to the evaluate_answer
function for evaluation (that does nothing for nowπ).
Struct and enums:
Right now, our enum doesn't contain any data! Just the variants names. One could be tempted to use his Struct knowledge and write something like this to include data, say the value of the answer:
enum SimpleAnswer {
True,
False,
}
struct UserAnswer {
value: String,
evaluation: SimpleAnswer,
}
fn main() {
let true_with_value = UserAnswer {
value: String::from("The sky is blue"),
evaluation: SimpleAnswer::True,
};
let false_with_value = UserAnswer {
value: String::from("The sky is brown"),
evaluation: SimpleAnswer::False,
};
}
This could work but it's too much typing π also there is a much simpler way to include data in enums. We can define an enum with as many variants as we want and with as many data bound to those variants as we want, take the follow as an example:
enum Superhero {
Batman, // No data, he is ... Batman
Superman(u32), // Current altitude as u32
WonderWoman { lasso_of_truth: bool, sword: bool }, // Struct-like data
Flash(u32, u32, u32), // Speedforce alarm levels as u32
GreenLantern(LanternCorps), // LanternCorps is a Struct!
}
Each variant can take its data in a different way, this is cool π! So, getting back to our "Answers" enum example, we could integrate the answer value as follows:
enum Answer {
True(String),
False(String),
}
fn main() {
// Enums with values
let true_with_value = Answer::True(String::from("The sky is blue"));
let false_with_value = Answer::False(String::from("The sky is brown"));
}
Implementing enum methods:
Similar to structs, we can bind enums with methods with the impl
keyword and use the &self
shorthand to pass the enum to the methods. For demonstrating this one, take a look at the following example implementing a Shape type similar to what I've mentioned at the beginning of this article:
enum Shape {
Rectangle { width: f32, height: f32 },
Circle(f32),
Square(f32),
}
impl Shape {
fn area(&self) -> f32 {
match self {
self::Shape::Rectangle {
width: w,
height: h,
} => w * h,
self::Shape::Circle(radius) => radius * radius * 3.14,
self::Shape::Square(side) => side * side,
}
}
fn get_type(&self) -> &str {
match self {
self::Shape::Rectangle {
width: _,
height: _,
} => "Rectangle",
self::Shape::Circle(_) => "Circle",
self::Shape::Square(_) => "Square",
}
}
}
fn main() {
let r = Shape::Rectangle {
width: 10.0,
height: 5.0,
};
let c = Shape::Circle(5.0);
let s = Shape::Square(4.0);
let shapes = [r, c, s];
for shape in shapes.iter() {
println!("{}: {}", shape.get_type(), shape.area())
}
}
There is a lot going on in this example so let's break it down π. First, we defined our Shape enum that has 3 variants, Rectangle, Circle, and Square. For the Rectangle, it has a struct-like data structure that accepts the width and height properties as floats (f32). For the Circle and Square, they only take one float value which is the radius and the side for each shape respectively.
Then we have implemented two methond bound to this Shape enum, the area
and get_type
. They use something called Pattern Matching with the match
keyword that we will discuss in detail in the next article. For now, just know that area
calculates the area of the shape and get_type
returns the shape type as a string literal.
Lastly, we have created 3 variables, r
, c
, and s
which are Rectangle, Circle, and Square variants of the Shape type respectively. Then we crammed all of them in an array called shapes
and we looped over this array printing the type and the area of each shape as follows:
Finished dev [unoptimized + debuginfo] target(s) in 14.11s
Running `target/debug/structs_basics`
Rectangle: 50
Circle: 78.5
Square: 16
That's it for now for enums. In the next one, we will dig deeper into the Pattern Matching that we glimpsed in this article. See you then π.
Top comments (0)