模式

模式由如下一些内容组合而成:

  • 字面量
  • 解构的数组、枚举、结构体或者元组
  • 变量
  • 通配符
  • 占位符

模式的位置

  • match 分支
  • if let 条件表达式
  • while let 条件循环
  • for 循环
  • let 语句
  • 函数参数

match

Rust 有一个match 极为强大的控制流标识符,将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码,match 表达式必须是 穷尽的,模式 _用于匹配不想列举出的所有情况.

  1. enum Coin {
  2. Penny,
  3. Nickel,
  4. Dime,
  5. Quarter,
  6. }
  7. fn value_in_cents(coin: Coin) -> u32 {
  8. match coin {
  9. Coin::Penny => 1,
  10. Coin::Nickel => 5,
  11. Coin::Dime => 10,
  12. Coin::Quarter => 25,
  13. }
  14. }

match 关键字后跟一个表达式。每个分支都是val => expression这种形式。当匹配到一个分支,它的表达式将被执行。match属于“模式匹配”的范畴.

match也是一个表达式,也就是说它可以用在let绑定的右侧或者其它直接用到表达式的地方:

  1. let x = 5;
  2. let number = match x {
  3. 1 => "one",
  4. 2 => "two",
  5. 3 => "three",
  6. 4 => "four",
  7. 5 => "five",
  8. _ => "something else",
  9. };

这是一个把一种类型的数据转换为另一个类型的好方法。

匹配枚举

  1. enum Message {
  2. Quit,
  3. ChangeColor(i32, i32, i32),
  4. Move { x: i32, y: i32 },
  5. Write(String),
  6. }
  7. fn quit() { /* ... */ }
  8. fn change_color(r: i32, g: i32, b: i32) { /* ... */ }
  9. fn move_cursor(x: i32, y: i32) { /* ... */ }
  10. fn process_message(msg: Message) {
  11. match msg {
  12. Message::Quit => quit(),
  13. Message::ChangeColor(r, g, b) => change_color(r, g, b),
  14. Message::Move { x: x, y: y } => move_cursor(x, y),
  15. Message::Write(s) => println!("{}", s),
  16. };
  17. }

匹配Option<T>

  1. fn plus_one(x: Option<i32>) -> Option<i32> {
  2. match x {
  3. None => None,
  4. Some(i) => Some(i + 1),
  5. }
  6. }
  7. let five = Some(5);
  8. let six = plus_one(five);
  9. let none = plus_one(None);

多个模式

你可以使用|匹配多个模式。

  1. let x = 1;
  2. match x {
  3. 1 | 2 => println!("one or two"),
  4. 3 => println!("three"),
  5. _ => println!("anything"),
  6. }

这会输出one or two

范围

你可以用...匹配一个范围的值,范围只允许用于数字或 char 值,因为编译器会在编译时检查范围不为空。char 和 数字值是 Rust 唯一知道范围是否为空的类型.

  1. let x = 5;
  2. match x {
  3. 1 ... 5 => println!("one through five"),
  4. _ => println!("something else"),
  5. }

这会输出one through five

范围经常用在整数和char上。

  1. let x = 'c';
  2. match x {
  3. 'a' ... 'j' => println!("early letter"),
  4. 'k' ... 'z' => println!("late letter"),
  5. _ => println!("something else"),
  6. }

这会输出something else

解构

  • 解构结构体和元组
  1. let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
  • 解构结构体
  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. fn main() {
  6. let p = Point { x: 0, y: 7 };
  7. let Point { x: a, y: b } = p;
  8. assert_eq!(0, a);
  9. assert_eq!(7, b);
  10. }
  11. //简化
  12. struct Point {
  13. x: i32,
  14. y: i32,
  15. }
  16. fn main() {
  17. let p = Point { x: 0, y: 7 };
  18. let Point { x, y } = p;
  19. assert_eq!(0, x);
  20. assert_eq!(7, y);
  21. }
  • 解构枚举
  1. enum Message {
  2. Quit,
  3. Move { x: i32, y: i32 },
  4. Write(String),
  5. ChangeColor(i32, i32, i32),
  6. }
  7. fn main() {
  8. let msg = Message::ChangeColor(0, 160, 255);
  9. match msg {
  10. Message::Quit => {
  11. println!("The Quit variant has no data to destructure.")
  12. },
  13. Message::Move { x, y } => {
  14. println!(
  15. "Move in the x direction {} and in the y direction {}",
  16. x,
  17. y
  18. );
  19. }
  20. Message::Write(text) => println!("Text message: {}", text),
  21. Message::ChangeColor(r, g, b) => {
  22. println!(
  23. "Change the color to red {}, green {}, and blue {}",
  24. r,
  25. g,
  26. b
  27. )
  28. }
  29. }
  30. }
  1. enum Color {
  2. Rgb(i32, i32, i32),
  3. Hsv(i32, i32, i32)
  4. }
  5. enum Message {
  6. Quit,
  7. Move { x: i32, y: i32 },
  8. Write(String),
  9. ChangeColor(Color),
  10. }
  11. fn main() {
  12. let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
  13. match msg {
  14. Message::ChangeColor(Color::Rgb(r, g, b)) => {
  15. println!(
  16. "Change the color to red {}, green {}, and blue {}",
  17. r,
  18. g,
  19. b
  20. )
  21. },
  22. Message::ChangeColor(Color::Hsv(h, s, v)) => {
  23. println!(
  24. "Change the color to hue {}, saturation {}, and value {}",
  25. h,
  26. s,
  27. v
  28. )
  29. }
  30. _ => ()
  31. }
  32. }
  • 解构引用
  1. #![allow(unused_variables)]
  2. fn main() {
  3. struct Point {
  4. x: i32,
  5. y: i32,
  6. }
  7. let points = vec![
  8. Point { x: 0, y: 0 },
  9. Point { x: 1, y: 5 },
  10. Point { x: 10, y: -3 },
  11. ];
  12. let sum_of_squares: i32 = points
  13. .iter()
  14. .map(|&Point { x, y }| x * x + y * y)
  15. .sum();
  16. }

.. 忽略匹配

如果你只关心部分值,我们不需要给它们都命名:

  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. let origin = Point { x: 0, y: 0 };
  6. match origin {
  7. Point { x, .. } => println!("x is {}", x),
  8. }

这会输出x is 0

你可以对任何成员进行这样的匹配:

  1. fn main() {
  2. let numbers = (2, 4, 8, 16, 32);
  3. match numbers {
  4. (first, .., last) => {
  5. println!("Some numbers: {}, {}", first, last);
  6. },
  7. }
  8. }

绑定

可以使用@把值绑定到名字上:

  1. let x = 1;
  2. match x {
  3. e @ 1 ... 5 => println!("got a range element {}", e),
  4. _ => println!("anything"),
  5. }

这会输出got a range element 1

  1. #[derive(Debug)]
  2. struct Person {
  3. name: Option<String>,
  4. }
  5. let name = "Steve".to_string();
  6. let mut x: Option<Person> = Some(Person { name: Some(name) });
  7. match x {
  8. Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),
  9. _ => {}
  10. }

这会输出 Some("Steve"),因为我们把Person里面的name绑定到a

如果你在使用|的同时也使用了@,你需要确保名字在每个模式的每一部分都绑定名字:

  1. let x = 5;
  2. match x {
  3. e @ 1 ... 5 | e @ 8 ... 10 => println!("got a range element {}", e),
  4. _ => println!("anything"),
  5. }

忽略绑定

你可以在模式中使用_来忽视它的类型和值。例如,这是一个Result<T, E>match

  1. # let some_value: Result<i32, &'static str> = Err("There was an error");
  2. match some_value {
  3. Ok(value) => println!("got a value: {}", value),
  4. Err(_) => println!("an error occurred"),
  5. }

在第一个分支,我们绑定了Ok变量中的值为value,不过在Err分支,我们用_来忽视特定的错误,而只是打印了一个通用的错误信息。

_在任何创建绑定的模式中都有效。这在忽略一个大大结构体的部分字段时很有用:

  1. fn coordinate() -> (i32, i32, i32) {
  2. // generate and return some sort of triple tuple
  3. # (1, 2, 3)
  4. }
  5. let (x, _, z) = coordinate();

这里,我们绑定元组第一个和最后一个元素为xz,不过省略了中间的元素。

相似的,你可以在模式中用..来忽略多个值。

  1. enum OptionalTuple {
  2. Value(i32, i32, i32),
  3. Missing,
  4. }
  5. let x = OptionalTuple::Value(5, -2, 3);
  6. match x {
  7. OptionalTuple::Value(..) => println!("Got a tuple!"),
  8. OptionalTuple::Missing => println!("No such luck."),
  9. }

这会打印Got a tuple!

refref mut

使用ref引用,这样值的所有权就不会移动到模式中的变量。通常,当您与模式匹配时,模式引入的变量将绑定到值。Rust的所有权规则意味着该值将被移动到match您使用该模式的地方或任何地方,借用值的方式是使用引用&,所以你可能会认为解决的办法是改变Some(name)为Some(&name),但是模式中的语法不会创建引用,但会匹配值中的现有引用。所以因为&在模式中已经具有这种含义,我们不能用于在模式中创建引用&。那么要在模式中创建引用,我们在新变量之前使用关键ref

  1. let robot_name = Some(String::from("Bors"));
  2. match robot_name {
  3. Some(ref name) => println!("Found a name: {}", name),
  4. None => (),
  5. }
  6. println!("robot_name is: {:?}", robot_name);
  7. //ref mut
  8. let mut robot_name = Some(String::from("Bors"));
  9. match robot_name {
  10. Some(ref mut name) => *name = String::from("Another name"),
  11. None => (),
  12. }
  13. println!("robot_name is: {:?}", robot_name);

额外条件

  1. let num = Some(4);
  2. match num {
  3. Some(x) if x < 5 => println!("less than five: {}", x),
  4. Some(x) => println!("{}", x),
  5. None => (),
  6. }
  7. //
  8. fn main() {
  9. let x = Some(5);
  10. let y = 10;
  11. match x {
  12. Some(50) => println!("Got 50"),
  13. Some(n) if n == y => println!("Matched, n = {:?}", n),
  14. _ => println!("Default case, x = {:?}", x),
  15. }
  16. println!("at the end: x = {:?}, y = {:?}", x, y);
  17. }
  18. //
  19. let x = 4;
  20. let y = false;
  21. match x {
  22. 4 | 5 | 6 if y => println!("yes"),
  23. _ => println!("no"),
  24. }

if let

我们有一些Option<T>。我们想让它是Some<T>时在其上调用一个函数,而它是None时什么也不做。这看起来像:

  1. let option = Some(5);
  2. fn foo(x: i32) { }
  3. match option {
  4. Some(x) => { foo(x) },
  5. None => {},
  6. }

我们并不一定要在这使用match,例如,我们可以使用if

  1. # let option = Some(5);
  2. # fn foo(x: i32) { }
  3. if option.is_some() {
  4. let x = option.unwrap();
  5. foo(x);
  6. }

可以使用if let来优雅地完成相同的功能:

  1. # let option = Some(5);
  2. # fn foo(x: i32) { }
  3. if let Some(x) = option {
  4. foo(x);
  5. }

如果一个模式匹配成功,它绑定任何值的合适的部分到模式的标识符中,并计算这个表达式。如果模式不匹配,啥也不会发生。

如果你想在模式不匹配时做点其他的,你可以使用else

  1. # let option = Some(5);
  2. # fn foo(x: i32) { }
  3. # fn bar() { }
  4. if let Some(x) = option {
  5. foo(x);
  6. } else {
  7. bar();
  8. }

while let

使用while let可以把类似这样的代码:

  1. let mut v = vec![1, 3, 5, 7, 11];
  2. loop {
  3. match v.pop() {
  4. Some(x) => println!("{}", x),
  5. None => break,
  6. }
  7. }

变成这样的代码:

  1. let mut v = vec![1, 3, 5, 7, 11];
  2. while let Some(x) = v.pop() {
  3. println!("{}", x);
  4. }