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),
}
Quit
has no data associated with it at allMove
has a struct associated with itWrite
has a string.ChangeColor
has 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 class
es 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;
}