Enums

fn main() {
    enum IpAddrKind {
        V4,
        V6,
    }

    struct IpAddr {
        kind: IpAddrKind,
        address: String,
    }

    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };
}

Enums works as Functions

And you can put data directly to the enum values:


// this snippet is simpler version of the above snippet
fn main() {
    enum IpAddr {
        V4(String),
        V6(String),
    }

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

Enums Vs Structs

There’s another advantage to using an enum rather than a struct: each variant can have different types and amounts of associated data.

fn main() {
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));
}

Store Structs in Enums

Example from Rust std lib:

#![allow(unused)]
fn main() {
    struct Ipv4Addr {
        // --snip--
    }

    struct Ipv6Addr {
        // --snip--
    }

    enum IpAddr {
        V4(Ipv4Addr),
        V6(Ipv6Addr),
    }
}

And another example:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

// above snippet is equivalent to below snippet 


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


Define Methods on Enums

fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }

    impl Message {
        // add method for Enum `Message`
        fn call(&self) {
            // method body would be defined here
        }
    }

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

Use Option Enum instead of null value

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

Rust does not have null type, instead use Option.

Compiler will make sure we handle that case before using the value.

fn main() {
    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y; // Error: no implementation for `i8 + Option<i8>`
}

The match Control Flow Construct

match seems very similar to an expression used with if, but there’s a big difference: with if, the expression needs to return a Boolean value, but here, it can return any type. The type of coin in this example is the Coin enum that we defined on the first line.

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,
    }
}

Patterns that Bind to Values

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {  // Works like a method
            // `{:?}` uses debug trait for output
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

fn main() {
    value_in_cents(Coin::Quarter(UsState::Alaska));
}


Matches must be Exhaustive

Matches must cover every possible cases.

    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            // All possible cases must be covered due to Option<T> type
            Some(i) => Some(i + 1),
            None => None, 
        }
    }

Catch-all Patterns (other)

let x = 5;

match x {
    1 => println!("x is 1"),
    2 => println!("x is 2"),

    // Catch All
    other => println!("x is something else: {}", other),
}

The _ Placeholder

Differences: No Binding for this case.

let value = 5;

match value {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Other value"),
}

if let: Shorthand for _ placeholder

fn main() {
    let config_max = Some(3u8);

    // Original match pattern
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }

    /*
    Shorthand with `if let`
    
    Format: 
        if let [arm] = [variable] { 
            ...
        } else {
            ...
        }  
    */
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }
}