这篇博客涉及到的知识点有 枚举的定义Option 枚举match 语法if let 语法

枚举的定义

Rust 中定义一个枚举,使用关键字 enum,看下面的代码

// 定义一个NPC类型枚举
enum NPCType {
    TalkNPC,
    WalkNPC,
    BattleNPC,
    TaskNPC,
}

enum GameState {
    OnLogin = 1,
    OnHall = 2,
    OnWorld,
    OnBattle,
    OnLogOut,
}

但是 Rust 的枚举是可以关联数据的,看下面的代码

enum CommandType {
    Move(i32, i32, i32),
    Jump(u32),
    ChangeColor(u8, u8, u8, u8),
    Smile,
}

fn main() {
    let move_command = CommandType::Move(10, -5, 3);
    let jump_command = CommandType::Jump(2);
    let change_color_command = CommandType::ChangeColor(110,128,55,255);
    let smile_command = CommandType::Smile;
}

上面的代码中,可以给枚举中的字段关联数据,例如,Move 关联了3个i32类型的数据。我们可以关联任何类型的数据,也可以是自己定义的结构体,或者另一个枚举,都是可以的。

Option 枚举

Rust 没有空值(Null),但是可以使用 Option 枚举,来表示有和没有的概念。Option 枚举有两个字段,一个是 Some, 一个是 None,而 Option 是泛型的,就是说它可以面向任何类型的值 Option<T>,用于表示任何类型的值,有值,或者没有值。Some(T) 表示有值,而 None 表示没有值,以 i32 为例子,看下面的代码

fn main() {
    let has_value: Option<i32> = Some(200);
    let none_value: Option<i32> = None;

    println!("has_value: {:?}", has_value);
    println!("none_value: {:?}", none_value);
}

Option 可以理解为盒子,Some(T) 表示有盒子,如果有盒子,那盒子里一定装了某种东西,也就是某种类型的值。而 None 表示没有盒子。上面的代码中,直接使用了 Some(200),而不是 Option::Some(200),是因为太常用,而被 Rust 包含在了 preload 中,所以我们可以省略前缀 Option::。在 Rust 的学习过程中,会经常遇到 Option。特别是函数的返回值。

fn main() {
    let girl_age = get_girl_age(true);
    println!("girl_age: {:?}", girl_age);   // Some(18)

    let boy_age = get_girl_age(false);
    println!("boy_age: {:?}", boy_age);     // None
}

fn get_girl_age(is_girl: bool) -> Option<u32> {
    if is_girl {
        Some(18)
    } else {
        None
    }
}

match 语法

match 语法有点类型其他语言的 switch 语法,用于条件匹配。直接看代码

enum CommandType {
    Move(i32, i32, i32),
    Jump(u32),
    ChangeColor(u8, u8, u8, u8),
    Smile,
}

fn main() {
    let move_command = CommandType::Move(10, 5, 2);
    process_command(&move_command);

    let change_color_command = CommandType::ChangeColor(154,220, 14, 255);
    process_command(&change_color_command);
}

fn process_command(command: &CommandType) {
    match command {
        CommandType::Move(x, y, z) => {
            println!("Player Move, x: {}  y: {}  z: {}", x, y, z);
        },
        CommandType::Jump(height) => {
            println!("Player Jump to: {}", height);
        },
        CommandType::ChangeColor(r, g, b, a) => {
            println!("player change color: ({}, {}, {}, {})", r, g, b, a);
        },
        CommandType::Smile => {
            println!("player smile");
        }
    }
}

上面的代码中,还是以 CommandType 枚举来举例。在 main 函数中定义了两个 CommandType 变量,分别使用不同的枚举,然后在 process_command 中,使用 match 语法匹配。在使用 match 匹配时, 一但类型匹配成功,就可以将其关联的值,绑定到某个变量上,然后在 match 逻辑中使用。例如上面代码中匹配上 CommandType::Move,Move 关联了三个 i32 类型的数据,所以在匹配时,就可以将关联的数据,绑定到 match 中的 x, y, z 三个变量上。

Rust 的 match 匹配必须覆盖所有的类型,如果逻辑中只关心某些类型,则可以使用 _ 忽略其他类型。

enum CommandType {
    Move(i32, i32, i32),
    Jump(u32),
    ChangeColor(u8, u8, u8, u8),
    Smile,
}

fn main() {
    let command = CommandType::Smile;

    match command {
        CommandType::Smile => {
            println!("Smile Command");
        },
        _ => {
            println!("Other Command");
        }
    }
}

if let 语法

相比于 match 匹配的繁琐,if let 语法让匹配更简洁一些

enum CommandType {
    Move(i32, i32, i32),
    Jump(u32),
    ChangeColor(u8, u8, u8, u8),
    Smile,
}

fn main() {

    let move_command = CommandType::Move(10, 5, 2);

    if let CommandType::Move(x, y, z) = move_command {
        println!("Player Move: x: {}, y: {}, z: {}", x, y, z);
    } else {
        println!("is not move command");
    }
}

上面的代码中,if let 可以理解为,将右边的 move_command 和左边的枚举类型匹配,如果能匹配上,则将 move_command中的数据绑定到 x, y, z 上,然后执行匹配成功的逻辑,也就是打印出 x, y, z 的值,如果类型匹配不上,则执行匹配不上的逻辑。

为了更好的理解,下面再看一个例子

fn main() {
    let x = 2;

    if x == 2 {
        println!("x = 2");
    } else {
        println!("x != 2");
    }

    if let 2 = x {
        println!("x = 2");
    } else {
        println!("x != 2");
    }
}

上面代码中 if 语句 和 if let 语句执行的打印是一样的,似乎是一个正向匹配测试,一个反向匹配测试的感觉。