1 January 2023
An enum, or enumerated type, is a data type defined by enumerating all of its values. Defining an enum is straight-forward in Rust:
enum Animal {
Cat,
Dog,
}
This defines a type Animal
and two constructors Cat
and Dog
that are used to define values of that type:
let a = Animal::Cat;
let is_it_a_cat = match a {
Animal::Cat => true,
Animal::Dog => false,
};
In C there’s a convention to prefix enum values with the name of the enum, so instead of
Cat
you’d have AnimalCat
or maybe ACat
. Having them scoped by
the type’s namepace means there’s no need to do that in Rust.
If we want to avoid the repeated Animal::
prefix, we can import the entire scope at the
top of the file:
use Animal::*;
This makes the code looks cleaner and simpler, but clutters up the namespace a little. I think having this option is good -- it lets the programmer decide whether prefixing the constructors with the type makes the code clearer or not.
Enums are represented by integers, starting with zero. We can convert the enum to an integer:
println!("{}", Animal::Cat as usize); // prints "0"
println!("{}", Animal::Dog as usize); // prints "1"
…but not the other way around. That makes sense: what would 99 as Animal
be?
We can also set the values explicitly, which makes sense when the constructors naturally map to numbers:
enum Month {
Jan = 1,
Feb,
Mar,
Apr,
May,
Jun,
Jul,
Aug,
Sep,
Oct,
Nov,
Dec,
}
To make the enum type more useful, we’ll want to implement some traits, probably starting with
Debug
and PartialEq
. Fortunately, both can be derived automatically:
#[derive(Debug, PartialEq)]
enum Animal {
Cat,
Dog,
}
This means we can compare values with ==
and use {:?}
to print them:
let a = Animal::Cat;
println!("It's a {:?}."); // prints "It's a Cat."
if a == Animal::Dog {
println!("It's a doggy!");
}
If there’s a natural order it also makes sense to derive PartialOrd
:
#[derive(Debug, PartialEq, PartialOrd)]
enum Priority {
ExtraLow,
Low,
Medium,
High,
ExtraHigh,
}
Now we can compare values with <
and friends:
let p1 = Priority::Low;
let p2 = Priority::Medium;
println!("{}", p2 > p1); // prints "true"
It also means we can use them in ranges:
fn high_priority(p: &Priority) -> bool {
(Priority::High..Priority::ExtraHigh).contains(a)
}
Those ranges are a bit limited, though. You can’t use them in match
expressions -- that
only works for numbers and characters -- and you can’t iterate over the range unless you manually
implement the Step
trait.