shagag
Frontend Engineer
by shagag

Enums and Pattern Matching

02/18/2025

word count:568

estimated reading time:3 minutes

Enums in Rust provide a powerful way to define a type by enumerating its possible variants. This feature allows for more expressive and type-safe code. Let’s explore how to create an enum:

enum IpAddrKind {
    v4,
    v6,
}

We can instantiate each of the two variants of IpAddrKind as follows:

let four = IpAddrKind::v4;
let six = IpAddrKind::v6;

Notice that both variables are of the same type, IpAddrKind, and are namespaced under it. This allows us to define a function that can accept any IpAddrKind variant:

fn route(ip_kind: IpAddrKind) {

}

route(IpAddrKind::v4);

Enums in Rust can also hold data directly within each variant, making them versatile and powerful:

enum IpAddr {
  V4(String),
  V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"))
let loopback = IpAddr::V6(String::from("::1"))

This approach is both simple and intuitive, as it allows us to attach data directly to each variant of the enum.

Let’s examine a more complex example:

enum message {
  quit,
  move { x:i32, y:32 },
  write(string),
  changecolor(i32, i32, i32)
}

The same functionality can be achieved using structs, but it requires more boilerplate code:

struct QuitMessage;
struct MoveMessage {
  x: i32,
  y:i32,
}
struct WriteMessage(String);
struct ChangeColorMessage(i32, i32, i32);

In this example, we created four different structs to represent what was previously a single enum. This approach is more complex and redundant. Additionally, defining a function to handle any of these message types is not as straightforward as it is with an enum.

We can also define methods on enums using impl blocks:

impl Message {
  fn call(&self) {
    //method body
  }
}

let m = Message::Write(String::from("hello"));
m.call();

The Option Enum

The Option enum, defined in the standard library, elegantly handles scenarios where a value might be present or absent. Unlike many other languages, Rust does not have a null feature, which often represents the absence of a value. Instead, Rust uses Option to explicitly handle such cases.

enum Option<T> {
  None,
  Some(T)
}

The Option enum is so fundamental that it is included in the Rust prelude, meaning you don’t need to explicitly bring it into scope. The two variants of Option are Some and None.


let some_number = Some(5);
let absent_number: Option<i32> = None

The type of some_number is Option<i32>. For absent_number, Rust requires us to annotate the option type explicitly because it is a None value.

Matches are exhaustive

Rust’s match expression is exhaustive, meaning that all possible cases must be handled for the code to be valid. This ensures that no potential scenario is overlooked.

Catch-All Patterns and the _ Placeholder

The underscore (_) is a special pattern in Rust that matches any value without binding it to a variable. It is particularly useful in match expressions when you want to handle all remaining cases without needing to use the values.

  1. Using _:
match some_value {
    0 => println!("Zero"),
    1 => println!("One"),
    _ => println!("Something else")  // Matches any other number
}
  1. Using a variable name to catch and bind the value:
match number {
    1 => println!("One"),
    2 => println!("Two"),
    n => println!("The number is: {}", n)  // Matches and binds the value to 'n'
}

Key differences between _ and a catch-all variable: