5分钟速读之Rust权威指南(三十八)模板语法

模式语法

模式匹配在很多地方都有用到,比如let声明变量,函数参数,match语句,if let等等,前面章节只是简单讲了一些基本使用方法,这一节我们来看一下所有的模式匹配方式和模式匹配的强大能力

匹配字面量

最简单的匹配能力,匹配字面量类型,例如下面i32类型:

let x = 1;
match x {
  1 => println!("1"),
  2 => println!("2"),
  3 => println!("3"),
  _ => println!("任何值"), // match需要穷举i32类型,_用来匹配所有类型
}
// 1
复制代码

匹配命名变量

这种匹配方式一般用于解构,另外match会为匹配的结果创建独立的作用域:

let x = Some(5);
let y = 10;
match x {
  Some(50) => println!("x = 50"),

  // 这里的y在独立的作用域,是绑定了Some中的值,而不是外边的y
  Some(y) => println!("y = {:?}", y),
  _ => println!("默认 x = {:?}", x),
}

// 这里的y仍然是10
println!("匹配结束后: x = {:?}, y = {:?}", x, y);
// y = 5
// 最终: x = Some(5), y = 10
复制代码

多个模式

还支持使用|符号,表示或者的关系:

let x = 1;
match x {
  1 | 2 => println!("1 或者 2"),
  3 => println!("3"),
  _ => println!("任何值"),
}
// 1 或者 2
复制代码

匹配范围

类似区间,还可以使用..=来表示匹配一个区间:

let x = 2;
match x {
	1..=5 => println!("x >= 1 并且 x <= 5"),
	_ => println!("任何值"),
}
// x >= 1 并且 x <= 5
复制代码

不仅是数字,可以用char类型来表示一个字符的区间:

let y = 'c';
match y {
  'a'..='d' => println!("a-d之间"),
  'e'..='h' => println!("e-h之间"),
  _ => println!("任何值"),
}
// a-d之间
复制代码

解构结构体

结构体也可以在match的匹配模式中,前面我们可能没讲到解构结构体,这里先看一下正常情况下解构结构体的方法:

struct Point {
  x: i32,
  y: i32,
}
let p = Point { x: 10, y: 15 };

// 等号左边是模式,右边是结构体
let Point { x, y } = p;
println!("x = {} y = {}", x, y);
// x = 10 y = 15
复制代码

解构的时候可以重命名结构体中字段:

let Point { x: a, y: b } = p;
println!("a = {} b = {}", a, b);
// a = 10 b = 15
复制代码

最后我们在match条件中解构,模式与上面一样:

let p = Point { x: 0, y: 15 };
match p {
  Point { x: 0, y } => println!("在y轴上, y = {}", y),
  Point { x, y: 0 } => println!("在x轴上, x = {}", x),
  Point { x, y } => println!("既不在x轴也不在y轴, x = {} y = {}", x, y),
}
// 在y轴上, y = 15
复制代码

解构枚举

结构体可以解构,当然枚举也不例外了:

enum Message {
  ChangeColor(i32, i32, i32),
}
let msg = Message::ChangeColor(0, 160, 255);

// 同样等号左边是模式,右边是枚举值
let Message::ChangeColor(r, g, b) = msg;
println!("r: {}, g: {}, b: {}", r,g,b);
// r: 0, g: 160, b: 255
复制代码

match语句中匹配枚举值的多种变体:

enum Message {
  Quit,
  Move { x: i32, y: i32 },
  Write(String),
  ChangeColor(i32, i32, i32),
}
let msg = Message::ChangeColor(0, 160, 255);
match msg {
  Message::Quit => println!("Quit"),
  Message::Move { x, y } => println!("Move x = {} y = {}", x, y),
  Message::Write(str) => println!("Write{}", str),
  Message::ChangeColor(r, g, b) => println!("ChangeColor r = {} g = {} b = {}", r, g, b)
}
// ChangeColor r = 0 g = 160 b = 255
复制代码

解构嵌套的结构体和枚举

解构还支持多层嵌套的解构,下面在Message枚举中嵌套Color枚举,然后在match语句中解构:

enum Color {
  Rgb(i32, i32, i32),
  Hsv(i32, i32, i32),
}

enum Message {
  Quit,
  Move { x: i32, y: i32 },
  Write(String),
  ChangeColor(Color),
}

// Message嵌套Color
let color = Color::Rgb(0, 160, 255);
let msg = Message::ChangeColor(color);

match msg {
  Message::Quit => println!("Quit"),
  Message::Move { x, y } => println!("Move x = {} y = {}", x, y),
  Message::Write(str) => println!("Write{}", str),
	
  // 解构嵌套Color
  Message::ChangeColor(Color::Rgb(r, g, b)) => println!("Color r = {} g = {} b = {}", r, g, b),
  Message::ChangeColor(Color::Hsv(h, s, v)) => println!("Color h = {} s = {} v = {}", h, s, v)
}
// Color r = 0 g = 160 b = 255
复制代码

解构嵌套的结构体和元组

下面使用一个元组将Point结构体嵌套,然后使用let将其解构:

let tuple = ((3, 10), Point { x: 3, y: -10 });
let ((a, b), Point { x, y}) = tuple;
println!("a = {} b = {} x = {} y = {}", a, b, x, y);
// a = 3 b = 10 x = 3 y = -10
复制代码

使用 _ 忽略整个值

有时候有些参数或者变量我们并不关心,这时可以使用_来进行忽略,防止编译器报提醒,其实前面在match语句中已经使用过了:

fn foo(_: i32, b: i32) {
  println!("b = {}", b);
}
foo(10, 20);
复制代码

_是不能直接使用的:

fn foo(_: i32, b: i32) {
  println!("_ = {} b = {}", _, b); // 报错,不允许使用 _
}
foo(10, 20);
复制代码

使用嵌套的_忽略部分值:

let mut value = Some(5);
let new_value = Some(10);
match (value, new_value) {
  // 匹配两个值都是Some的情况,但不关心具体是什么值
  (Some(_), Some(_)) => {
    println!("value已经存在,不能覆盖");
  },
  // 如果两者中有一个None,那么就更新value
  _ => {
    value = new_value;
  }
}
println!("被更新之后的值 {:?}", value);
// 被更新之后的值 Some(5)
复制代码

一个模式中的多处使用_来忽略特定值:

let arr = [1, 2, 3, 4, 5];
match arr {
  [a, _, b, _, c] => println!("a = {} b = {} c = {}", a, b, c)
}
// a = 1 b = 3 c = 5
复制代码

变量名称使用_开头来忽略未使用的警告:

let _x = 1;
let y = 2; // warning: 未使用的变量y
// 上边的y被警告,而_x没有
复制代码

__开头变量的区别,_变量会绑定变量:

let s = Some(String::from("Hello!"));
if let Some(_s) = s {
  println!("匹配了字符串");
}
println!("{:?}", s); // error,s的所有权已经被转移到if let的条件内
复制代码

_并不会绑定变量的值:

let s = Some(String::from("Hello!"));
if let Some(_) = s {
  println!("匹配了字符串");
}
println!("{:?}", s);
// Some("Hello!")
复制代码

使用 .. 忽略剩余的值

_可以忽略单个值,但是如果在结构体时,我们如果只关心其中某一个属性,此时其他的属性不可能都用_来忽略,因为太多了,此时我们可以使用..来忽略结构体中的多个值:

struct Point {
  x: i32,
  y: i32,
  z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
  // 忽略除x外的所有值
  Point { x, .. } => println!("x = {}", x),
}
// x = 0
复制代码

忽略元组中的值:

let nums = (1, 2, 3, 4, 5);
match nums {
	// 取第一个和最后一个值,忽略中间的所有值
	(a, .., b) => println!("a = {} b = {}", a, b)
}
// a = 1 b = 5
复制代码

..不能有歧义:

let nums = (1, 2, 3, 4, 5);
match nums {
  // 报错,因为a有歧义,可能是一组数中的任意位置
	(.., a, ..) => println!("a = {}", a)
}
复制代码

匹配守卫提供的额外条件

匹配守卫(match guard)是一个指定于match分支模式之后的额外if条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况:

let num = Some(4);
match num {
  // 要求num是Some类型,并且x小于5
  Some(x) if x < 5 => println!("小于5: {}", x),
  Some(x) => println!("{}", x),
  None => (),
}
// 小于5: 4
复制代码

允许获取外部变量:

let x = Some(5);
let y = 10;
match x {
  Some(50) => println!("x = 50"),
  // 可以获取外部的y进行比较
  Some(n) if n == y => println!("与y相等, n = {}", n),
	_ => println!("默认 x = {:?}", x),
}
println!("最终: x = {:?}, y = {}", x, y);
// 最终: x = Some(5), y = 10
复制代码

与多个模式结合使用:

let x = 4;
let y = false;
match x {
  // 首先与 2 | 3 | 4 进行匹配,然后在判断条件 if y
  2 | 3 | 4 if y => println!("yes"),
  _ => println!("no"),
}
// no
复制代码

@绑定

at 运算符@允许我们在结构体、枚举中使用有条件的匹配,下面在枚举中使用@

enum Message {
  Hello { id: i32 }
}
let msg = Message::Hello { id: 10 };
match msg {
  // 对id创建别名并添加限制在1-10之间
  Message::Hello { id: value@ 1..=10 } => println!("id 在1-10之间 {}", value),
  Message::Hello { id: value@ 11..=20 } => println!("id 在11-20之间 {}", value),
  Message::Hello { id } => println!("id不在1-20之间 {}", id)
}
复制代码

在结构体中使用@

struct User {
  name: String,
  age: u8
}
let user = User { name: "xiaoming".to_string(), age: 10 };
match user {
  User { name, age: value @ 1..=15 } => println!("{} 年龄在1-15之间 {}", name, value),
  User { name, age } => println!("{} 年龄不在1-15之间 {}", name, age)
}
// xiaoming 年龄在1-15之间 10
复制代码

看了这么多例子是不是体会到了rust的模式匹配的强大,尤其是在match语句中,可以帮我们少写很多条件判断,建议大家把上面的例子都试着写一下,加深一下印象,才能运用的熟练。

封面图:跟着Tina画美国

关注「码生笔谈」公众号,阅读更多最新章节

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享