Enums and Pattern Matching

Tuple Variants

A tuple variant is an enum variant that carries one or more unnamed positional data fields enclosed in parentheses VariantName(T1, T2, ...). Just as a unit variant mirrors a unit-like struct (struct Quit;), a tuple variant mirrors a tuple struct (struct Write(String);) embedded directly inside an enum.

Declaring and Constructing Tuple Variants

Each tuple variant can hold a different number and combination of field types:

#[derive(Debug, PartialEq)]
enum IpAddress {
    V4(u8, u8, u8, u8), // 4 positional `u8` fields
    V6(String),         // 1 positional `String` field
}

fn main() {
    let home = IpAddress::V4(127, 0, 0, 1);
    let loopback_v6 = IpAddress::V6(String::from("::1"));

    println!("IPv4: {home:?}");
    println!("IPv6: {loopback_v6:?}");
}
IPv4: V4(127, 0, 0, 1)
IPv6: V6("::1")

Extracting Tuple Variant Fields via Pattern Matching

Unlike a standalone tuple struct (struct Color(u8, u8, u8);)—where you can write color.0 directly—you cannot use dot-index syntax (home.0) on an enum value because the compiler does not know at compile time which variant the enum currently holds!

Instead, you unpack the positional fields of a tuple variant using pattern matching (match, if let, let ... else, or while let):

enum IpAddress {
    V4(u8, u8, u8, u8),
    V6(String),
}

fn format_ip(ip: &IpAddress) -> String {
    match ip {
        IpAddress::V4(a, b, c, d) => format!("{a}.{b}.{c}.{d}"),
        IpAddress::V6(addr) => addr.clone(),
    }
}

fn is_private_ipv4(ip: &IpAddress) -> bool {
    match ip {
        // Match specific literal values (`10`, `192, 168`) and ignore others with `..`:
        IpAddress::V4(10, ..) => true,
        IpAddress::V4(192, 168, ..) => true,
        IpAddress::V4(172, second, ..) if (16..=31).contains(second) => true,
        _ => false,
    }
}

fn main() {
    let local = IpAddress::V4(192, 168, 1, 50);
    let public = IpAddress::V4(8, 8, 8, 8);

    println!("{} is private: {}", format_ip(&local), is_private_ipv4(&local));
    println!("{} is private: {}", format_ip(&public), is_private_ipv4(&public));
}
192.168.1.50 is private: true
8.8.8.8 is private: false

Standard Library Tuple Variants (Some, Ok, Err)

The most widely used tuple variants in Rust are the 1-element tuple variants of Option<T> and Result<T, E> in the standard library prelude:

pub enum Option<T> {
    None,       // Unit variant
    Some(T),    // 1-element Tuple variant!
}

pub enum Result<T, E> {
    Ok(T),      // 1-element Tuple variant!
    Err(E),     // 1-element Tuple variant!
}

Tuple Variants Are Also First-Class Constructor Functions

Whenever you define a tuple variant EnumName::Variant(T1, T2), Rust automatically creates a constructor function with the signature fn(T1, T2) -> EnumName.

This allows you to pass Some, Ok, Err, or any custom tuple variant directly as a function pointer to .map(), .and_then(), or higher-order functions without writing a closure |x| EnumName::Variant(x):

#[derive(Debug)]
enum Token {
    Number(i32),
    Word(String),
}

fn main() {
    let raw_numbers = [10, 20, 30];

    // Pass `Token::Number` (`fn(i32) -> Token`) directly to `.map()`:
    let tokens: [Token; 3] = raw_numbers.map(Token::Number);

    // Wrap an iterator of strings into `Token::Word`:
    let word_tokens: Vec<Token> = ["let", "fn"]
        .into_iter()
        .map(String::from)
        .map(Token::Word)
        .collect();

    println!("Number tokens: {tokens:?}");
    println!("Word tokens:   {word_tokens:?}");
}
Number tokens: [Number(10), Number(20), Number(30)]
Word tokens:   [Word("let"), Word("fn")]

##Wrapping a Standalone Struct Inside a 1-Element Tuple Variant

When an enum variant has many fields or needs to be passed around as its own standalone type to helper functions, a common design pattern is to define a separate struct for that variant's data and wrap it inside a 1-element tuple variant (Variant(MyStruct)):

#[derive(Debug)]
struct TlsConfig {
    cert_path: String,
    port: u16,
}

#[derive(Debug)]
enum Transport {
    Plaintext(u16),
    Encrypted(TlsConfig), // 1-element tuple variant wrapping `TlsConfig`!
}

// Now helper functions can accept `&TlsConfig` directly as a type!
fn start_tls_server(cfg: &TlsConfig) {
    println!("Starting TLS on port {} with cert '{}'", cfg.port, cfg.cert_path);
}

fn main() {
    let mode = Transport::Encrypted(TlsConfig {
        cert_path: String::from("/etc/ssl/server.pem"),
        port: 443,
    });

    if let Transport::Encrypted(ref tls_cfg) = mode {
        start_tls_server(tls_cfg);
    }
}
Starting TLS on port 443 with cert '/etc/ssl/server.pem'