Enums and Pattern Matching
Defining an Enum
enum IpAddrKind {
V4,
V6,
}
Enum Values
We can create instances like this:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
We can store additional data inside enums:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
One cool advantage, each instance can have different types of data associated with it:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
There is a standard library for this!
Another example of an enum with a wide variety of types in its variants:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Quithas no data associated with it at allMovehas a struct associated with itWritehas a string.ChangeColorhas 3i32.
You can just create structs for the variants above, but they wouldn't be grouped together:
struct QuitMessage;
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String);
struct ChangeColorMessage(i32, i32, i32);
but it's more annoying to create a function that takes these 4 types. With an enum, its a single type, Message.
Another similarity - we can define methods on enums using impl.
impl Message {
fn call(&self) {
//method
}
}
let m = Message::Write(String::from("hello"))
m.call()
The Option Enum and Its Advantage Over Null Values
Basically, the argument for Option in Scala. Rust has defined its own Option:
enum Option<T> {
Some(T),
None,
}
Option<T> is an enum, and Some(T) and None are variants of the enum. (<T> is the syntax for generics. Later chapter.)
The match Control Flow Operator
Basically the match in Scala.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Pattern that Bind to Value
We can extract values using match, kinda like how we can extract values from case classes in Scala.
Let's say we have
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
We can extract the UsState:
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
Matching with Option<T>
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Matches Are Exhaustive
Unlike Scala, matches in Rust has to be exhaustive. There will be a compile error if it isn't:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
} //Won't compile
The _ Placeholder
Similar to Scala, _ can be used as a catch all:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
Concise Control Flow with if let
Rust combines if and let to allow a more concise way to handle values that match one pattern while ignoring the rest.
For example, this is pretty wordy:
let some_value = Some(u08);
match some_value {
case Some(3) => println!("triple"),
_ => (),
}
We only care for Some(3). We can use if let:
if let Some(3) = some_value {
println!("triple");
}
if let takes a pattern and an expression separated by an =.
You can use else in addition:
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}