Rust 模式匹配

205 阅读5分钟

模式匹配

match 控制流结构

在 Rust 中match 是一个极其强大的控制流运算符,它允许我们将一个值和一系列的模式进行比较,并根据相匹配的模式执行相应代码

Rust 中的匹配是穷尽的,穷举到最后的可能性来使代码有效

为什么说它很强大?

得益于编译器,match 确保了所有可能的情况都得到了处理。

整个 match 表达式像一个分类器,match 的每一个模式像一个管道一样,不同的值都会掉入符合大小的管道中,进入相关联的代码块并在执行中被使用。

#![allow(unused)]
fn main() {
  enum Apple {
    HeavyApple,
    MiddleApple,
    LightApple,
    BadApple
  }

  fn classify_apple(apple: Apple) -> u8 {
    match apple {
      Apple::HeavyApple => 3,
      Apple::MiddleApple => 2,
      Apple::LightApple => 1,
      Apple::BadApple => 0
    }
  }
}

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

如果分支代码较长,则需要使用大括号;较短时,则不需要使用大括号。

#![allow(unused)]
fn main() {
  enum Apple {
    HeavyApple,
    MiddleApple,
    LightApple,
    BadApple
  }

  fn classify_apple(apple: Apple) -> u8 {
    match apple {
      Apple::HeavyApple => {
        println!("This is a heavy apple");
        3
      },
      Apple::MiddleApple => {
        println!("This is a middle apple");
        2
      },
      Apple::LightApple => 1,
      Apple::BadApple => 0
    }
  }
}

绑定值的模式

绑定匹配模式的部分值,也就是如何从枚举成员中提取值

#![allow(unused)]
fn main() {
  enum Plate {
    BigPlate,
    MiddlePlate,
    SmallPlate,
    GetApple(Apple)
  }

  #[derive(Debug)]
  enum Apple {
    HeavyApple,
    MiddleApple,
    LightApple,
    BadApple
  }

  fn classify_apple(plate: Plate) -> u8 {
    match plate {
      Plate::BigPlate => {
        println!("This is a big plate");
        3
      },
      Plate::MiddlePlate => {
        println!("This is a middle plate");
        2
      },
      Plate::SmallPlate => 1,
      Plate::GetApple(apple) => {
        println!("Get the Apple is {:?}", apple);
        0
      }
    }
  }

  classify_apple(Plate::GetApple(Apple::HeavyApple));
}

在上方的代码中,我们在最后调用了 classify_apple 这个函数,并且匹配到了 Plate::GetApple 这个分支模式。在这个模式中,增加了一个变量 apple ,这个变量将会绑定 Apple::HeavyApple 这个枚举成员。这也就达到了从 Plate 枚举中提取到了值。

当然,最终的输出为:

Get the Apple is HeavyApple

匹配 Option<T>

匹配 Option<T> ,实际上比较的是 Option<T> 的成员。

#![allow(unused)]
fn main() {
  fn plus(x: Option<i32>) -> Option<i32> {
    match x {
      None => None,
      Some(i) => Some(i + 1),
    }
  }

  let zero = Some(0);
  let one = plus(zero);
  let none = plus(None);

  println!("zero is {:?}, one is {:?}, none is {:?}", zero, one, none);
}

输出:

zero is Some(0), one is Some(1), none is None

匹配 Some(T)

在上方代码中,这两行代码表示:

let zero = Some(0);
let one = plus(zero);

plus 函数中的 x 的值是 Some(0),接着执行程序,其将与每个分支进行比较。

进入 match 表达式中,值 Some(0) 并不与 None 匹配,则进入下一个分支模式。

Some(i) => Some(i + 1)

Some(0)Some(i) 匹配成功,所以 i 绑定了 0 ,值为 0。接着分支的代码被执行,将 i 的值加一并返回一个值为 6 的 新 Some 中。

接着执行程序,来到了这一行代码:

let none = plus(None);

执行 plus 函数,进入 match 表达式

None => None

匹配成功,程序结束并返回 => 右侧的值 None ,其他分支将不再进行比较。

通配模式

通配模式常用于对一些特定的值采取特殊操作,对其他值采取默认操作的场景。

#![allow(unused)]
fn main() {
  let guess_number = 10;

  match guess_number {
      -1 => println!("too small"),
      8 => println!("so close"),
      other => continue_guess(other),
  }

  fn continue_guess(num: i32) {
    println!("continue guess {}", num);
  }
}

输出:

continue guess 10

前两个分支涵盖了 -18 的情况,最后一个分支涵盖了所有其他可能的值。即使没有枚举出 i32 所有可能的值,但是这段代码依然可以通过编译,因为最后一个模式将匹配所有未被特殊列出的值,起到了兜底的作用。这种模式即是通配模式,它满足了 match 必须被穷尽的要求。

⚠️值得注意的是:通配分支必须放在最后,否则 Rust 会警告通配分支之后的其他分支将永远不会被匹配到!

占位模式

当不想使用通配模式获取值的时候,Rust 提供了另一个模式:_这是一个特殊的模式,可以匹配任意值而不绑定到该值

#![allow(unused)]
fn main() {
  let guess_number = 10;

  match guess_number {
      -1 => println!("too small"),
      8 => println!("so close"),
      _ => continue_guess(),
    	// 或者直接返回一个空元组
      _ => (),
  }

  fn continue_guess() {
    println!("continue");
  }
}

if let 简洁控制流

if let 是一种更短的控制流编写方式。

上方代码可以使用 if let 简化为以下代码:

#![allow(unused)]
fn main() {
  let guess_number = Some(10);

  if let Some(num) = guess_number {
    println!("num is {}", num);
  }
}

输出:

num is 10

if let 语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 是完全一样的。

在这里,模式是 Some(num) ,表达式是 guess_number

表达式对应的是 match,而模式对应的则是第一个分支。模式不匹配时,if let 块中的代码不会执行。

if let 虽然简洁,但是会失去 match 强制要求的穷尽性检查。所以需要在使用时,去权衡利弊了。

可以在 if let 结构中添加一个 else ,对应着 match 中的 _ 模式。

#![allow(unused)]
fn main() {
  enum Plate {
    BigPlate,
    MiddlePlate,
    SmallPlate,
    GetApple(Apple)
  }

  #[derive(Debug)]
  enum Apple {
    HeavyApple,
    MiddleApple,
    LightApple,
    BadApple
  }

  fn classify_apple(plate: Plate) -> () {
    let big_plate = Plate::BigPlate;
    let mut count = 0;

    if let Plate::GetApple(apple) = big_plate {
      println!("Get Apple is {:?}", apple);
    } else {
      count += 1;
    }

    println!("num is {}", count)
  }

  classify_apple(Plate::GetApple(Apple::HeavyApple));
}

输出为:

num is 1

当枚举包含数据时,可以根据需要处理多少的情况来选择使用 matchif let 来获取并使用这些值。