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.