写点什么

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

用户头像
码生笔谈
关注
发布于: 3 小时前
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仍然是10println!("匹配结束后: 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嵌套Colorlet 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画美国


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

发布于: 3 小时前阅读数: 3
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(三十八)模板语法