八.枚举与模式匹配

定义枚举

enum IpAddrKind {
    V4,
    V6,
}

枚举值

枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
fn route(ip_type: 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(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 。
struct QuitMessage; // 类单元结构体
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体

这里回顾下相关概念

元组:let tup: (i32, f64, u8) = (500, 6.4, 1);

类单元结构体:struct QuitMessage;

元组结构体:struct Color(i32, i32, i32);

元组只有类型没有名称,且都是以()定义,而结构体都是以{}定义,除了元组结构体。

C++的枚举只支持值类型的,Rust的枚举应该是支持了更多类型。

# enum Message {
#     Quit,
#     Move { x: i32, y: i32 },
#     Write(String),
#     ChangeColor(i32, i32, i32),
# }
#
impl Message {
    fn call(&self) {
        // 在这里定义方法体
    }
}
let m = Message::Write(String::from("hello"));
m.call();

这里的write也没看见实现,反正只是返回一个枚举对象,暂时不管。

前面结构体已经用到过self,等同于c++的this理解,默认的unmut等同于C++的成员函数后面加const,且一般传递&self等同于传递的是this对象本身的引用,只传self因为所有权变更只在特定情况下才能使用。

Option 枚举和其相对于空值的优势

Option 是标准库定义的另一个枚举。 Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。

Rust 并没有很多其他语言中有的空值功能。

空值概念上讲是一个因为某种原因目前无效或缺失的值。由于空值引起的程序问题并非空值本身存在问题,而在于具体的实现。

Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option

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

Some(T) 和 None 仍是 Option 的成员。

目前需要知道的就是 意味着 Option 枚举的 Some 成员可以包含任意类型的数据

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

如果使用 None 而不是 Some ,需要告诉 Rust Option 是什么类型的,因为编译器只通
过 None 值无法推断出 Some 成员保存的值的类型。

当有个 None 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么, Option 为什么就比空值要好呢?

简而言之,因为 Option 和 T (这里 T 可以是任何类型)是不同的类型,编译器不允
许像一个肯定有效的值那样使用 Option

let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;//错误,类型不匹配
^ no implementation for `i8 + std::option::Option<i8>`

错误信息意味着 Rust 不知道该如何将 Option 与 i8 相加,因为它们的类型不同。

当在 Rust 中拥有一个像 i8 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 Option (或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况

为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 Option 中。

Rust默认定义的变量如果为空则直接编译报错,这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。

当有一个 Option 的值时,如何从 Some 成员中取出 T 的值来使用它,后面再看。

match 控制流运算符

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

以上语义上基本上是类似C++形式的枚举switch。

=> 运算符将模式和将要运行的代码分开,每一个分支之间使用逗号分隔。如果分支代码较短的话通常不使用大括号。

绑定值的模式

匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中
提取值的。

#[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) => {
            println!("State quarter from {:?}!", state);
            25
        },
    }
}
value_in_cents(Coin::Quarter(UsState::Alaska))

value_in_cents(Coin::Quarter(UsState::Alaska)) , Coin::Quarter(UsState::Alaska) 作为形参coin的实参。当将值与每个分支相比较时,匹配到Coin::Quarter(state) 。这时, state 绑定的将会是值 UsState::Alaska 。

匹配 Option和匹配 Some(T)

Option是类型,Some(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);

匹配是穷尽的

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
         Some(i) => Some(i + 1),//错误^ pattern `None` not covered
    }
}

Rust 中的匹配是 穷尽的(exhaustive):必须穷举到最后的可能性来使代码有效。

_ 通配符

Rust 也提供了一个模式用于不想列举出所有可能值的场景

let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

_ 模式会匹配所有的值。通过将其放置于其他分支之后, _ 将会匹配所有之前没有指定的
可能的值。 () 就是 unit 值,所以 _ 的情况什么也不会发生。

match 在只关心 一个 情况的场景中可能就有点啰嗦了。为此 Rust 提供了 if let

if let 简单控制流

处理只匹配一个模式的值而忽略其他模式的情况。

let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
	println!("three");
}
let some_u8_value = Some(0u8);
match some_u8_value {
    Some(3) => println!("three"),
    _ => (),
}
let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} 
else {
    count += 1;
}

Coin::Quarter(state)为枚举成员,coin为实际要比较的实参。

上一篇:LeetCode-322.Coin Change


下一篇:LeetCode Coin Change Series