Enums in Rust, short for “enumerations,” are a powerful feature that allow you to define a type by enumerating its possible variants. Enums are useful for representing data that can be one of several different forms
Syntax
An enum is defined using the enum keyword, followed by the name of the enum and a set of variants enclosed in curly braces {}. Each variant can optionally hold data.
enum Direction {
North,
South,
East,
West,
}
Enums can be useful to represent data in possible forms. For example - As of present the two used standard for IP protocols are IPv4 and IPv6. This can be easily be defined using enums and then matched using rust’s match
operator when needed.
enum IpAddrKind {
V4,
V6,
}
You can create instances of an enum by using one of its variants:
let ipv4 = IpAddrKind::V4;
Enums can store data
Enums can also hold data. Each variant can have different types and amounts of associated data. This makes enums more flexible than simple constants.
enum WebEvent {
PageLoad, // No data
KeyPress(char), // Single character
Click { x: i64, y: i64 }, // Named fields
}
let load = WebEvent::PageLoad;
let press = WebEvent::KeyPress('a');
let click = WebEvent::Click { x: 100, y: 200 };
Some inbuilt enums
Rust standard libraries has defined some enums for being used such as the Option
enum or the Result
enum.
Option<T>
enum
Option<T>
enum is implemented in Rust to not use NULL
in as the way to showcase the absense of values as in other languages. Use of NULL
is considered bad as using a null values as a non-null values can lead to many problems.
Due to this reason, the rust’s standard library instead of implementing NULL
have implemented an Option<T>
enum. It is used to represent a value that can either be something (Some)
or nothing (None)
. This is particularly useful for handling situations where a value might be absent, without resorting to null pointers or other error-prone mechanisms.
enum Option<T> {
None,
Some<T>,
}
Why Use Option?
- The Option enum is used to handle cases where a value might not exist. For example:
- When looking up a key in a map, the key might not exist.
- When parsing user input, the input might be invalid.
- When accessing an element in a collection, the index might be out of bounds. By using Option, Rust forces you to explicitly handle both cases (Some and None), which helps prevent runtime errors like null pointer exceptions.
match
Control Flow Construct
Rust has an extremely powerful control flow construct called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches.
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,
}
}
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);
Catching all patterns and the _ placeholder
Using match construct can be really powerful when have to match one val with other and then execute something.
the other
and _
are catch-all patterns to deal with match’s we didnt specifically define.
Other
- Named catch-all
This is used when we want to store the value of the expression that didnt match previous expressions.
fn main() {
let number = 7;
match number {
1 => println!("One"),
2 => println!("Two"),
other => println!("Number: {}", other),
}
}
_
- wild card pattern
This is used when we just want to handle the mismatching but dont wanna store the value of amtch express
fn main() {
let number = 7;
match number {
1 => println!("One"),
2 => println!("Two"),
_ => println!("Other number"), // `_` catches all other values
}
}
Control flow with if let
The if let syntax lets you combine if and let into a less verbose way to handle values that match one pattern while ignoring the rest
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}