Enums and Pattern Matching
In this chapter, we'll explore define types by enumerating their possible variants.
Enums and Pattern Matching
Enumerations, or enums, allow you to define a type by enumerating its possible variants. Where structs give you a way of grouping related fields and data, enums give you a way of saying a value is one of a possible set of values.
In this chapter, we'll look at:
- Defining an enum
- The
matchcontrol flow construct - Concise control flow with
if letandlet else
Defining an Enum
Let's look at a situation where enums are more appropriate than structs. Say we need to work with IP addresses. Currently, two major standards are used: version four and version six. These are the only possibilities—we can enumerate all possible variants:
enum IpAddrKind {
V4,
V6,
}IpAddrKind is now a custom data type we can use in our code.
Enum Values
Create instances of each variant:
let four = IpAddrKind::V4
let six = IpAddrKind::V6Both values are of the same type: IpAddrKind. We can define a function that takes any IpAddrKind:
fn route(ip_kind: IpAddrKind) -> void {
// ...
}
route(IpAddrKind::V4)
route(IpAddrKind::V6)Enum Variants with Data
Enums can store data directly in each variant:
enum IpAddr {
V4(string),
V6(string),
}
let home = IpAddr::V4("127.0.0.1")
let loopback = IpAddr::V6("::1")Each variant can have different types and amounts of data:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(string),
}
let home = IpAddr::V4(127, 0, 0, 1)
let loopback = IpAddr::V6("::1")Complex Enum Examples
Enums can hold any kind of data—strings, numeric types, structs, or even other enums:
enum Message {
Quit,
Move { x: int, y: int },
Write(string),
ChangeColor(int, int, int),
}This enum has four variants:
Quithas no dataMovehas named fields like a structWriteincludes a singlestringChangeColorincludes threei32values
Methods on Enums
We can define methods on enums with impl:
impl Message {
fn call(self) -> void {
match self {
Message::Quit => println("Quit"),
Message::Move { x, y } => println("Move to (", x, ", ", y, ")"),
Message::Write(text) => println("Write: ", text),
Message::ChangeColor(r, g, b) => println("Color: ", r, ",", g, ",", b),
}
}
}
let m = Message::Write("hello")
m.call()The Option Enum
Mana doesn't have null. Instead, it has Option<T> to express that a value might be something or nothing:
enum Option<T> {
Some(T),
None,
}The Option<T> enum is so useful it's included in the prelude—you don't need to import it. Its variants Some and None can be used directly:
let some_number = Some(5)
let some_char = Some('e')
let absent_number: Option<int> = NoneWhen we have a Some value, we know a value is present. When we have None, it means no valid value. Why is this better than null?
Because Option<T> and T are different types. The compiler won't let us use an Option<T> as if it were definitely a valid T:
let x: i8 = 5
let y: Option<i8> = Some(5)
let sum = x + y // Error: cannot add Option<i8> to i8To use the inner value, we must explicitly handle the case where it's None. This eliminates one of the most common bugs in programming: assuming something isn't null when it actually is.
To extract the value from an Option<T>, we use pattern matching. Continue to The match Control Flow Construct to learn how.