Skip to content

枚举

Rust 的枚举是一个常用且实用的功能, 不同于其他语言的枚举

Rust 的 enum 也被称为标签联合(tagged-union)

rust
// Rust 的枚举和其他语言的枚举不同,
// 每个可枚举字段不仅仅是一个固定的值,
// 它可以包含更多数据, 更像一个特殊的 struct
#[derive(Debug)]
enum Message {
    Quit,                       // 不关联任何数据
    Move {                      // 类似结构体的数据, 包含命名字段
        x: i32,
        y: i32,
    },
    Write(String),              // 包含一个字符串
    ChangeColor(i32, i32, i32), // 包含多个i32的数据
}

impl Message {
    // 可以给枚举定义方法
    fn f1(&self) {
        println!("f1 called");
    }
}

fn main() {
    let m1 = Message::Write(String::from("str"));
    m1.f1();

    let m2 = Message::Move { x: 1, y: 2 };
    dbg!(m2);
}

match 模式匹配

Rust 的模式匹配非常像其他编程语言的 switch/case 语句

不同的是, match 语句必须要让值是穷尽的, 也就是说没有匹配到的情况也必须要加上

rust
fn main() {
    let s = 11;

    match s {
        1 => println!("1"),
        // 一次匹配多个值, 类似
        // case 3:
        // case 5:
        // case 7:
        //    break;
        3|5|7 => println!("3,5,7,9"),

        // 匹配一个范围内的数字
        10..20 => println!("10-20"),
        _ => println!("unknown"),
    }
}
rust
fn main() {
    // 使用标准库的 Option 枚举
    // let x: Option<i32> = None;
    let x: Option<i32> = Some(1);
    // let x: Option<i32> = Some(11);
    // let x: Option<i32> = Some(2);

    match x {
        // case None 的情况
        None => println!("x value is none"),
        // case 1 的情况
        Some(1) => {
            println!("x = 1");
        },
        // case 11 的情况
        Some(11) => {
            println!("x = 11");
        },
        // _ 代表 switch-case 最后那个 default
        // 当没有匹配到上面任何一个 case 的时候执行
        _ => println!("not matched"),
    }
}
rust
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter,
}

fn main() {
    let s = Season::Summer;

    match s {
        Season::Spring => println!("春"),
        Season::Summer => println!("夏"),
        Season::Autumn => println!("秋"),
        Season::Winter => println!("冬"),
        _ => println!("unknown"),
    }
}
rust
fn main() {
    let x = Some(5);

    match x {
        // 不能这样匹配:
        // 第一个匹配项是 i32
        // 第二个匹配项是 Option 枚举
        1 => println!("matched: x = 1"),
        Some(5) => println!("matched: x = 5"),
        _ => println!("default, matched value is {:?}", x),
    }
}

if let 简洁控制流

rust
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter,
}

fn main() {
    let x = 11;

    // 普通值可以这样判断
    if x == 11 {
        println!("if: x=11");
    }


    // 如果是个枚举, 就无法直接 if 判断, 以为类型不一样
    // 虽然可以用 match 来判断, 但是就一种情况, 感觉有些麻烦
    let s = Some(11);
    // match s {
    //     Some(11) => println!("match: x=11"),
    //     _ => (),
    // }

    // if let 的写法等同于上面的 match 写法
    // 这个表达式中的 = 不是赋值的意思
    // 注意必须 Some(xx) 在前, 要判断的值在后,
    // 否则报错, 且判断有误, 就像这样, 也能进入 if
    // if let s = Some(15) {
    //     println!("if-let:x=15");
    // }
    if let Some(12) = s {
        println!("if-let:x=11");
    } else {
        println!("if-let: else");
    }

    // if let 也可以用于自定义枚举
    // 还可以多分支形式的判断
    let summer = Season::Summer;
    if let Season::Spring = summer {
        println!("Season::Spring");
    } else if let Season::Summer = summer {
        println!("Season::Summer");
    } else {
        println!("not Season::Summer");
    }
}

match 和 if let 的区别

match 必须穷尽所有可能, if let 则不需要

  • match 类似其他 C-Like 语言的 switch case 语句
  • if let 则类似 if ...elseif...else 语句
rust
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter,
}

fn main() {
    let s = Season::Summer;

    match s {
        Season::Spring => println!("春"),
        Season::Summer => println!("夏"),
        // Season::Autumn => println!("秋"),
        // Season::Winter => println!("冬"),
        _ => println!("unknown"),
        // _ 代表枚举其他任何可能的值,
        // 1. 要么将所有的值列举出来, 来实现穷尽所有枚举可能的值
        // 2. 要么设置一个默认的, 如果没有匹配到, 就使用这个默认的处理
        // 3. 这个 match 等价于 =>
        // if let Season::Spring = s {
        //     println!("春");
        // } else if let Season::Summer = s {
        //     println!("夏");
        // } else {
        //     println!("unknown");
        // }
    }

    // 但是 if let 可以不穷尽枚举所有可能的值,
    // 只匹配枚举的其中值的一个进行处理
    if let Season::Summer = s {
        println!("夏");
    }
}

while let 简洁循环控制流

rust
fn main() {
    let mut stk = Vec::new();

    stk.push(1);
    stk.push(3);
    stk.push(5);

    // 1. stk.pop 返回的是一个 Option 枚举
    //    1.1 有值就是: Some(val)
    //    1.2 没值就是: None
    // 2. 只要 stk.pop 匹配的是 Some(val) 而不是 None,
    //    这个循环的条件就会一直成立
    while let Some(item) = stk.pop() {
        println!("pop item is: {}", item);
    }
}

模式

  • let 模式: let pattern = expression;
  • for 模式: for pattern in expression {}
  • 函数参数: fn f1((x, y): (u32, u32)) {}
rust
fn f1((x,y): (u32, u32)) {
    println!("x:{}, y:{}", x, y);
}

fn main() {
    // 类似 JS 的结构语法
    let (x, y) = (1, 2);
    println!("x: {} y:{}", x, y);

    // 警告: unnecessary parentheses around pattern
    // 翻译: 模式周围不闭包的括号, 因为编译器会将代码
    // 识别为:  let m = (3, 4);
    let (m) = (3, 4);


    // for 模式
    let items = vec![1, 2, 3];
    for (index, item) in items.iter().enumerate() {
        println!("items[{}]: {}", index, item);
    }

    // 函数参数, 传入元组
    f1((1, 2));
}

模式的两种形式

  • 不可辩驳的: 能匹配任何可能传递的值得模式(赋值语句/函数参数)
  • 可以辩驳的: 都某些可能的值, 无法进行匹配(if let/while let)
rust
fn main() {
    // 这就是不可辩驳的, 无论右边的值是什么, 都能匹配上
    let x = 1;
    println!("{}", x);

    // 这就是可辩驳的, 如果右边的值可能会是 None
    // 是 None 的情况下就无法匹配成功
    let o = Some(1);
    // let o: Option<i32> = None;
    if let Some(num) = o {
        println!("{}", num);
    }

    // 不能这样赋值, 赋值语句和函数参数必须是不可辩驳的模式
    // 编译器报错: error[E0005]: refutable pattern in local binding
    // let x: Option<i32> = Some(2);
    // let Some(value) = x;

    // 不能这样来做判断, if let 和 while let 必须是可辩驳的模式
    // 虽然能够通过编译, 但是编译器会报警告: irrefutable `if let` pattern
    // 因为这样写是没有意义的, 永运都是可以匹配上的, 不可能会失败
    if let x = 5 {
        println!("x: {}", x);
    }
}

模式匹配语法

直接匹配字面值

rust
fn main() {
    let x = 2;
    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

匹配命名变量

rust
fn main() {
    let x = Some(5);

    match x {
        Some(1) => println!("matched: x = 1"),

        // 此时的 x 只要是任意 Some(x) 的值
        // 那么被 Some 包裹的值是什么, value 就是什么
        Some(value) => println!("matched: x = {}", value),

        // 编译器警告: unreachable pattern
        // 注意: 匹配是从上倒下一项一项匹配
        // 因为 Some(value) 肯定在前, 如果 Some(6) 可以匹配成功,
        // 那么 Some(value) 这个匹配命名变量一定会匹配成功
        // 也就是说, Some(6) 在匹配命名变量项后面, 不可能会执行到
        Some(6) => println!("matched: x = 6"),

        // 上面所有项都没有匹配成功的情况:
        _ => println!("default, matched value is {:?}", x),
    }
}

多种模式

也就是之前学的一次匹配多个值, 使用 | 语法

rust
fn main() {
    let x = 5;

    match x {
        1|3|5|7 => println!("x is one of them: one, three, five, seven"),
        2|4|6|8 => println!("x is one of them: two, four, six, eight"),
        _ => println!("default, matched value is {:?}", x),
    }
}

匹配范围

匹配一个值是否在一个范围中

rust
fn main() {
    let x = 20;

    match x {
        // 1..=10: 表示 1-10 包括最后的10
        1..=10 => println!("x is between 1 and 10"),

        // 11..20: 表示 11-19 不包括最后的20
        11..20 => println!("x is between 11 and 20"),

        _ => println!("default, x value is {:?}", x),
    }

    // char 类型也可以使用范围匹配
    let c = 'a';
    match c {
        'a'..='m' => println!("letter between in a-m"),
        'n'..='z' => println!("letter between in n-z"),
        _ => println!("ASCII letter not in a-z"),
    }
}

解构分解值

可以使用模式来结构 struct enum tumple 从而引用这些类型的值得不同部分

rust
struct Position {
    x: i32,
    y: i32,
}

fn main() {
    let p = Position { x: 1, y: 2 };

    // 类似 JS 的解构赋值语法
    let Position { x, y } = p;
    println!("{} {}", x, y);

    // 类似 JS 的解构赋值语法的重命名
    let Position { x: px, y: py } = p;
    println!("{} {}", px, py);

    match p {
        // y必须是0, x的值随意
        Position { x, y: 0 } => println!("x: {}", x),

        // x必须是0, y的值随意
        Position { x: 0, y } => println!("y: {}", y),

        // x 和 y 都可以随意
        Position { x, y } => println!("x:{} and y:{}", x, y),
    }
}
rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit                 => println!("匹配不能存值的枚举项"),
        Message::Move { x, y: 0 }     => println!( "匹配类似结构体的枚举项,但y必须为0: {}", x),
        Message::Move { x, y }        => println!( "匹配类似结构体的枚举项: {}, {}", x, y),
        Message::Write(text)          => println!("匹配类似元组的枚举项:{}", text),
        Message::ChangeColor(r, g, b) => println!("匹配类似元组的枚举项(多个值), {}, {}, {}", r, g, b)
    }
}
rust
enum Color {
   Rgb(i32, i32, i32),
   Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color), // 嵌套的枚举
}

fn main() {
    let x = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match x {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!( "r:{}, g:{}, b: {}", r, g, b)
        },
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!( "h:{}, s:{}, v: {}", h, s, v)
        }
        _ => println!("Nothing to do"),
    }
}
rust
struct Position {
    x: i32,
    y: i32,
}

fn main() {
    // 解构需要一一对应
    let ((element, is_in_map), Position{x, y}) = (("box", true), Position { x: 1, y: 2 });
    println!("element is:{}", element);
    println!("element is in map:{}", is_in_map);
    println!("element position:x={}, y={}", x, y);
}

忽略模式中的值

rust
struct Position {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    // _ 忽略未使用的变量/索引位置
    let x = 1;
    let _y = 2; // 忽略未使用的变量,否则会有警告, unused variable `y`
    println!("x: {}", x);

    let nums = (1, 3, 5, 7);
    let (one, _, five, _) = nums; // 解构赋值时, 用 _ 忽略不需要的值
    println!("one: {}, five: {}", one, five);

    // .. 忽略字段/对应索引位置
    let pos = Position { x: 1, y: 2, z: 3 };
    match pos {
        // x,y,z 字段的值必须都是0
        Position { x:0, y:0, z:0 } => {
            println!("Origin");
        },

        // x 的字段必须是0, 其他所有字段的值都忽略
        Position { x: 0, .. } => {
            println!("On x axis");
        },

        _ => println!("other"),
    }

    let nums = (1, 3, 5, 7);
    match nums {
        (1, .., 5) => {
            println!("元组第一个元素必须是1, 最后一个元素必须是5");
        },
        (5, ..) => {
            println!("元组第一个元素必须是5");
        },
        (.., 5) => {
            println!("元组最后一个元素必须是5");
        },
        (first, .., last) => {
            println!("元组最后一个元素和第一个元素可以是任何值, {}, {}", first, last);
        },
    }
}

使用 match 守卫来提供额外的条件

可以理解为其他语言的if语句有多个条件

rust
fn main() {
    let x = Some(3);

    match x {
        // Some(v) 表示匹配任何 Some(x) 的值, 但是 v 必须满足 v < 5 这个条件
        Some(v) if v < 5 => println!("less than five: {}", v),

        // Some(v) 表示匹配任何 Some(x) 的值
        Some(v) => println!("x:{}", v),

        // None 的情况
        None => println!("none"),
    }
}

@ 绑定范围变量的值

rust
struct User {
    id: i32,
}

enum Season {
    Spring(u8),
    Summer(u8),
    Autumn(u8),
    Winter(u8),
}

fn main() {
    let x = 5;

    // 绑定普通值
    match x {
        v @ 1..10 => println!("x value is {}", v),
        _ => println!("x value is not between 1 and 10"),
    }

    // 绑定 struct 字段
    let u = User { id: 6 };
    match u {
        User { id: x @ 1..10 } => println!("user id is {}", x),
        _ => println!("user id is not between 1 and 10"),
    }

    // 绑定枚举
    let s = Season::Spring(7);
    match s {
        Season::Spring(x @ 1..10) => println!("spring value is {}", x),
        _ => println!("spring value is not between 1 and 10"),
    }
}

Released under the MIT License.