Skip to content

第九节、枚举

枚举

定义:

enum IpAddrKind {
    V4,
    V6,
}
这是一个IP地址的例子,有IPv4和IPv6。

枚举的值

    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
定义两个变量等于枚举里面的某个值,这里使用的是"::"符号。

作为函数的参数类型,定义如下:

fn route(ip_kind: IpAddrKind) {
 ....
}
调用示例:
    route(IpAddrKind::V4);
    route(IpAddrKind::V6);

结构体里使用枚举

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"),
    };

字符串类型的枚举

可以直接将数据放在相同类型的使用相同枚举中,而不是使用结构体。代码如下:

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

    let home = IpAddr::V4(String::from("127.0.0.1"));

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

枚举还可以使用不同类型的字段,代码如下:

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

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

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

还可以个更复杂的,把结构体放到枚举里:

struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

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

还可以镶嵌多种类型:

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

这个枚举有四种不同类型的变体:

  • Quit根本没有与之关联的数据。
  • Move像结构一样命名字段。
  • Write包括一个String.
  • ChangeColor包括三个i32值。

枚举和impl实现块

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

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

Option

Option是Rust标准库中一个非常常见和有用的枚举,Option的价值是实现了一个Null,很多编程语言都需要有Null的表达。Rust就用Option来实现了它。在标准库里定义如下:

enum Option<T> {
    None,
    Some(T),
}
语法是我们还没有讨论过的 Rust 的一个特性。它是一个泛型类型参数,我们将在第 10 章更详细地介绍泛型。现在,你只需要知道这意味着枚举的Some变体 Option可以保存任何类型的数据,并且每个具体类型被用来代替T使整体Option类型成为不同的类型。以下是一些使用Option值来保存数字类型和字符串类型的示例:
    let some_number = Some(5);
    let some_string = Some("a string");

    let absent_number: Option<i32> = None;

枚举+match做流控

这个操作类似switch...case。

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,
    }
}
match的代码可以是执行一个语句块:
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

双层枚举做流控

#[derive(Debug)] // so we can inspect the state in a minute
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) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}
...
value_in_cents(Coin::Quarter(UsState::Alaska)) 使用方法
...

搭配Option

    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);

在做match的时候,每一项都不能少,下面的代码会报错:

    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            Some(i) => Some(i + 1),
        }
    }
因为None没有做match。

通过match 模式匹配 ,实现一个掷骰子的游戏:

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}
掷3就是加一个帽子,7就拿走一顶帽子,其他的就移动。

这里要说的是other其他的,other是值match里面值之外的,这里就是3,7之外。 要是在other后面加一个5,

    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
        5 => 5,
    }
那么会怎样呢? 答案是执行5,而不是other。和这个类似的还有一个"_", 它代表所有值,任何值。
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => anything(),
        5 => 5,
    }

这个代码,除了3,7之外都会走"", 那么3,7会不会走""呢?答案是不会,因为match是按照顺序执行的。那么5呢?5不会被执行,因为走了"_",这一点和"other"是有区别的。

简洁的if let流控

   let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }
可以简单的合并表达如下:
    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }

再看一个例子:

    let mut count = 0;
    match coin {
        Coin::Quarter(state) => println!("State quarter from {:?}!", state),
        _ => count += 1,
    }
if let表示如下:
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count += 1;
    }