match和if let

rust中最常用的模式匹配 一个match的例子:

enum Direction{
	East,
	West,
	North,
	South
}
fn main(){
	let dire = Direction::South;
	match dire{
		Direction::East=>Println!("East"),
		Direction::North|Direction::South=>{
			println!("South or North");	
		},
		_=>println!("West")
		};
}

Notice:

  • match的匹配必须穷举出所有可能,一次这里_表示未列出来的所有可能性
  • match的每一个分支都必须是一个表达式,且所有分支的表达式返回的类型必须相同
  • X|Y,类似于逻辑或 其实match和switch十分相似

match匹配

基本格式:

match target {
    模式1 => 表达式1,
    模式2 => {
        语句1;
        语句2;
        表达式2
    },
    _ => 表达式3
}

将模式和target进行匹配,就是模式匹配 match允许我们将一个值和一系列的模式相比较,并且根据相匹配的模式执行对应代码:

enum Coin{
	Penny,
	Nickel,
	Dime,
	Quarter,
}

fn value_in_cents(coin:Coin)->u8{
	match coin{
		Coin::Penny=>{
			println!("Lucky penny!");
			1
		},
		Coin::Nickel=>5,
		Coin::Dime=>10,
		Coin::Quarter=>25,
	}
}

match后的表达式可以是任何值,只要和后面分支中能对应上 每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个match表达式的返回值;如果分支有多行代码,那么就需要用{}包裹,同时最后一行代码需要是一行表达式

使用match表达式幅值

match本身也是一个表达式,可以用于赋值

enum IpAddr{
	Ipv4,
	Ipv6
}
fn main(){
	let ip1 = IpAddr::Ipv6;
	let ip_str = match ip1{
		IpAddr::Ipv4=>"127.0.0.1",
		_=>"::1",
	};
	
	println!("{}",ip_str);
}

模式绑定

从模式中取出绑定的值

#[derive(Debug)]
enum UsState{
	Alabama,
	Alaska,
}
enum Coin{
	Penny,
	Nickel,
	Dime,
	Quarter(UsState),
}

其中Coin::Quarter成员还存放了一个值; 下面,我们希望在模式匹配中,获取到25美分硬币上刻印的州的名称:

fn value_in_cents(coin:Coin)->u8{
	match coin{
		Coin::Penny=>1,
		Coin::Nickel=>5,
		Coin::Dime=>10,
		Coin::Quarter(state)=>{
			println!("State quarter from {:?}!",state);
			25
		},
	}
}

上面代码中,在匹配Coin::Quarter(state)模式时,将内部存储的值绑定到了state变量上,因此state变量就是对应的Usstate枚举类型 再来看一个更复杂的例子:

enum Action{
	Say(String),
	MoveTo(i32,i32),
	ChangeColorRGB(u16,u16,u16),
}
fn main(){
	let actions = [
		Action::Say("Hello Rust".to_string()),
		Action::MoveTo(1,2),
		ACtion::ChangeColorRGB(255,255,0),
	];
	for action in actions{
		match action{
			Action::Say(s)=>{
				println!("{}",s);
			},
			Action::MoveTo(x,y)->{
				println!("point from (0,0) move to ({},{}"),x,y);
			},
			Action::ChangeColorRGB(r,g,_)=>{
				println!("change color into'(r:{},g:{},b:0)','b' has been ignored",r,g,);
			}
			}
	}
}

和上面的state相同,也是将内部存储的值绑定到(s),(x,y),(r,g,b)上

$cargo run
Log:

穷尽匹配

match的匹配必须穷尽所有情况:

enum Direction{
	East,
	West,
	North,
	South,
}
fn main(){
	let dire = Direction::South;
	match dire{
		Direction::East => println!("East");
		Direction::North!Direction::South=>{
			println!("South or North");
		},
	};
}

Error:

_通配符

不想在匹配时列出所有值的时候,可以使用rust提供的一个特殊格式

let some_u8_value = 0u8;
match some_u8_value{
	1 => println!("one"),
	3 => println!("three"),
	5 => println!("five"),
	7 => println!("seven"),
	_ => (),
}

除了_,用一个变量来承载其他情况也是可以的:

#[derive(Debug)]
enum Direction{
	East,
	West,
	North,
	South,
}
fn main(){
	let dire = Direction::South;
	match dire{
		Direction::East => println!("East"),
		other =>println!("other direction:{:?}",other),
	};
}

if let匹配

有时候会遇到一个模式的值需要被处理,其他值直接忽略的场景,如果用match:

let v = Some(3u8);
match v{
	Some(3)=>println!("three"),
	_=>(),
	}

很冗余对吧 可以用if let的方式实现

if let Some(3)-v{
	println!("three");
}

当需要匹配一个条件时用if let,否则用match

matches!宏

rust提供了一个很实用的宏:macthes!,可以将1个表达式和模式进行匹配,然后返回匹配的结果true或false

enum MyEnum{
	Foo,
	Bar
}
fn main(){
	let v =vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];
}

对v进行过滤,只保留值是MyEnum::Foo的元素,可以这么写:

v.iter().filter(|x|x==MyEnum::Foo);

Error,因为无法将x直接和一个枚举对象进行比较;但是可以用match来完成,但是代码很哆嗦,这时候用

v.iter.filter(|x| macthes!(x,MyEnum::Foo));

再看看其他例子:

let foo = 'f';
assert!(macthes!(foo,'A'..='Z'|'a'..='z'));

let bar = Some(4)
assert!(macthes!(bar,Some(x) if x>2));

变量遮蔽

match和if let都是一个新的代码块,而且这里的绑定相当于新变量,如果使用同名变量,会发生变量遮蔽:

fn main(){
	let age = Some(30);
	println!("匹配前{:?}",age);
	if let Some(age) = age{
		println!("匹配出{}",age);
	}
	println!("匹配后{:?}",age);
}

if let中,=右边的Some(i32)类型的age被左边的i32类型的新age遮蔽了,一直持续到if let语句块的结束

对于match也是如此:

fn main(){
	let age = Some(30);
	println!("匹配前,age是{:?}",age);
	match age{
		Some(age)=>println!("匹配出的age是{}",age),
		_=>()
	}
	println!("匹配后,age是{:?}",age);
}

match中的变量遮蔽并不那么容易看出来,因此需要小心,尽量避免同名

fn main(){
	let age = Some(30);
	println!("匹配前,age{:?}",age);
	match age{
		Some(x) => println!("匹配中{}",x),
		_=>()
	}
	println!("匹配后,age{:?}",age);
}

结构Option

enum Optoin<T>{
	None,
	Some(T),
}

因为Option,Some,None都包含在prelude中,因此可以直接通过名称来使用,无需以Option::Some形式来使用

匹配Option<T>

使用Option<T>,是为了从Some中取得内部的T以及处理没有值的情况,下面来编写一个函数,获取一个Option<i32>,若其中含有一个值,则加1;若其中没有值,则返回None

fn plus_one(x:Option<i32>)=>Option<i32>{
	match x{
		None=>None,
		Some(i)=>Some(i+1),
	}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None)

模式使用场景

模式一般由:

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

所有可能用到模式的地方

match分支

match VALUE{
	PATTERN => EXPRESSION,
	PATTERN => EXPRESSION,
	PATTERN => EXPRESSION,
}

match上每一个分支就是一个模式,因为match是穷尽式的,所以我们总需要1个特殊的模式_,匹配剩余的所有情况:

match VALUE{
	PATTERN => EXPRESSION,
	PATTREN => EXPRESSION,
	_ => EXPRESSION,
}

if let 分支

if let PATTERN=SOME_VALUE{
	
}

while let 条件循环

允许只要模式匹配就一直进行while循环:

let mut stack = Vec::new();'

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top)=stack.pop(){
	println!("{}",top);
}

for循环

let v = vec!['a','b','c'];

for (index,value) in v.iter().enumerate(){
	println!("{} is at index {}",value,index);
}

Let

也是一种模式匹配

let PATTERN = EXPRESSION;

其中x也是一种模式绑定,将匹配的值绑定到变量x,因此在rust中,变量名也是一种模式 模式匹配会要求两边的类型相同:

let (x,y) = (1,2,3);

Error:

函数参数

也是模式:

fn foo(x:i32){
	
}

其中x就是一个模式,还可以在参数中匹配模式:

fn print_coordinates(&(x,y):&(i32,i32)){
	println!("Current location:({},{}}",x,y);
}
fn main(){
	let point = (3,5);
	print_coordinates(&point);
}

&(3,5会匹配模式,x->3,y->5

let和if let

对于下面代码:

let Some(x)=some_option_value;

会报错,因为右边的值可能不是Some而是None,这时候就能进行匹配,也就是上面的代码遗漏了None的匹配 类似let,for和match都必须要求完全覆盖匹配,才能通过编译 但是对于if let,可以这样使用:

if let Some(x) = some_option_value{
	println!("{}",x);
}

因为if let允许匹配一种模式,而忽略其余的模式

let-else(rust1.65新增)

使用let-else匹配,即可使let变为可驳模式,可以使用else分支来处理模式不匹配的情况,但是else分支中必须使用发散的代码块处理 Like this:

use std::str::FromStr;

fn get_count_item(s:&str)->(u64,&str){
	let mut it = s.split(' ');
	let (Some(count_str),Some(item))=(it.next(),it.next())else{
		panic!("Can't segment count item pair:'{s}'");
	}
	let Ok(count)=u64::from_str(count_str)else{
		panic!("Can't parse integer:'{count_str}'");
	(count,item)
	};
	// error:'else' clause of 'let..else' does not diverge
	// let Ok(count)=u64::from_str(count_str)else{0};
}
fn main(){
	assert_eq!(get_count_item("3 chars"),(3,"chairs"));
}

let-else的一个显著特点在于解包成功时所创建的变量具有更广的作用域

if let Some(x) = some_option_value{
	println!("{}",x);
}
let Some(x) = some_option_value else{return;};
println!("{}",x);

上面if let写法中的x只能在if分支中使用,而if-else写法中的x可以在let之外使用

全模式列表

匹配字面值

let x = 1;

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

匹配命名变量

匹配命名变量时会遇到:

fn main(){
	let x =Some(5);
	let y =10;
	match x{
		Some(50)=>println!("Got 50"),
		Some(y)=>println!("Matched,y={:?}",y,
		_=>println!("Default case,x={:?}",x),
	}
	println!("at the end:x={:?},y={:?}",x,y);
}

第2个匹配分支引入了一个新变量y,会匹配任何Some中的值,因为这里的y在match表达式的作用域中,而不是之前main作用域中,所以是一个新变量,不是开头声明的10。这个新变量y绑定会匹配任何Some中的值,这里是x中的值

单分支多模式

在match表达式中,可以使用|匹配多个模式,表示或

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

通过..=匹配值的范围

..=允许匹配一个闭区间序列内的值,如下代码中,模式匹配任何在序列中的值,该分支会执行:

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

序列只允许用于数字或者字符类型,原因是:可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是rust中仅有的可以用于判断是否为空的类型 下面是1个使用字符类型序列的例子:

let x ='c';
match x{
	'a'..='f'=>println!("early ASCII letter"),
	'k'..='z'=>println!("late ASCII letter"),
	_ =>println!("something else"),
}

解构并分解值

也可以使用模式来解构结构体、枚举、元组、数组和引用

解构结构体

下面展示如何用let解构一个含x和y的结构体Point:

struct Point{
	x:i32,
	y:i32,
}
fn main(){
	let p = Point{x:0,y:7};
	
	let Point {x:a,y:b}=p;
	assert_eq!(0,a);
	assert_eq!(7,b);
}

模式中的变量名不必和解构体中的字段名一致 因为变量名匹配字段名是常见的,因为let Point {x:x,y:y}=p;中x和y最常见,所以对于匹配结构体字段的模式存在简写:只需要列出结构体字段的名称,则模式创建的变量会有相同的名称。

struct Point{
	x:i32,
	y:i32,
}
fn main(){
	let p = Point{x:0,y:7};
	let Point{x,y}=p;
	assert_eq!(0,x);
	assert_eq!(7,y);
}

有时候不需要为所有的字段创建变量,可以使用字面值作为结构体模式的一部分进行解构:

# struct Point{
#     x: i32,
#     y: i32,
#}
# 
fn main(){
	let p = Point{x:0,y:7};
	
	match p{
		Point{x,y:0}=>println!("On the x axis at {}",x);
		Point{x:0,y}=>println!("On the y axis at {}",y);
		Point{x,y}=>println!("On neither axis:({},{})",x,y),
	}
}

首先是 match 第一个分支,指定匹配 y 为 0 的 Point; 然后第二个分支在第一个分支之后,匹配 y 不为 0,x 为 0 的 Point; 最后一个分支匹配 x 不为 0,y 也不为 0 的 Point。

解构枚举

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!("The Quit variant has no data to destructure.")
		}
		Message::Move{x,y}=?{
			println!("Move in the x direction {} and in the y direction {}",x,y);
		}
		Message::Write(text)=>println!("Text message:{}",text),
		Message::ChangeColor(r,g,b)->{
			println!(
			"Change the color to red{},green{},and blue{}",r,g,b
			)
		}
	}
}

模式匹配一样需要类型相同,因此匹配Message::Move{1,2}这样的枚举值,必须用Message::Move{x,y}这样的同类型

对于像Message::Quit这样没有任何数据的枚举成员,不能进一步解构,只能匹配字面值Message::Quit,因此模式中没有任何变量

解构嵌套的结构体和枚举

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 msg = Message::ChangeColor(Color::Hsv(0,160,255));
	match msg{
		Message::ChangeColor(Color::Rgb(r,g,b))=>{
			println!(
			"Change the color to red{},green{},and blue{}",r,g,b)
		}
		Message::ChangeColor(Color::Hsv(h,s,v))=>{
			println!(
			"Change the color to hue{},stauration{},and value{}",h,s,v
			)
		}
		_=>()
	}
}

解构结构体和元组

我们可以用复杂的方式混合、匹配和嵌套解构模式,以下是一个复杂的结构体:

struct Point{
	x:i32,
	y:i32,
}
let ((feet,inches),Point{x,y})=((3,10),Point{x:3,y:-10});

解构数组

定长

let arr;[u16;2]=[114,514];
let [x,y]=arr;

assert_eq!(x,114);
assert_eq!(y,514);

不定长

let arr:&[u16]=&[114,514];

if let [x,...]=arr{
	assert_eq!(x,&114);
}
if let &[..,y]=arr{
	assert_eq!(y,514);
}
let arr:&[u16]=&[]
assert!(matches!(arr,[..]));
assert!(!matches!(arr,[x,..]));

忽略模式中的值

_忽略整个值

fn foo(_:i32,y:i32){
	println!("This code only uses the y parameter:{}",y);
}
fn main(){
	foo(3,4);
}

有些时候不需要特定函数参数时,最好修改签名不再包含无用的参数

使用嵌套的_忽略部分值

可以在一个模式内使用_忽略部分值

let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value,new_setting_value){
	(Some(),Some())->{
		println!("Can't overwrite an existing customized value");
	_=>{
		setting_value = new_setting_value;
	}
	}
}
println!("setting is {:?}",setting_value);

还可以在一个模式中的多处使用下划线来忽略特定值,如下所示,这里忽略了一个五元元组中的第二和第四个值:

let numbers = (2, 4, 8, 16, 32);

match numbers {
    (first, _, third, _, fifth) => {
        println!("Some numbers: {}, {}, {}", first, third, fifth)
    },
}

使用下划线开头忽略未使用的变量

若是创建了1个变量但是却不在任何地方使用,rust通常会给出警告,因为者可能是个BUG. 希望告诉rust不要警告未使用的变量:

fn main(){
	let _x = 5;
	let y = 10;
}

Notice:_x仍会将值绑定到变量,而_则完全不会绑定

let s = Some(String::from("Hello!"));

if let Some(_s)=s{
	println!("found a string");
}
println!("{:?}",s);

上面s的值会被转移给_s 只使用下划线则不会:

let s = Some(String::from("Hello"));

if let Some(_)=s{
	println!("found a string");
}
println!("{:?}",s);

用..忽略剩余值

对于有多个部分的值,可以使用..来使用部分值而忽略部分值

struct Point{
	x:i32,
	y:i32,
	z:i32,
}
let origin = Point{x:0,y:0,z:0};

match origin{
	Point{x,..}=>println!("x is {}",x),
}

或是忽略中间值

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

然而使用..必须是无歧义的

fn main(){
	let numbers = (2,4,8,16,32);
	
	match numbers{
		(..,second,..)=>{
			println!("Some numbers:{}",second)
		},
	}
}

Error:

Match guard

match guard是一个位于match分支模式之后的额外if条件,能为分支模式提供进一步的匹配条件 可以使用模式中创建的变量:

let num = Some(4);

match num{
	Some(x) if x<5 => println!("less than five:{}",x),
	Some(x)=> println!("{}",x);
	None => (),
}

match表达式的模式种新建了一个变量而不是使用match之外的同名变量,内部变量覆盖了外部变量,意味着此时不能够使用外部变量的值,下面的代码展示了如何用匹配守卫修复:

fn main(){
	let x = Some(5);
	let y = 10;
	
	match x{
		Some(50)=>println!("Got 50"),
		Some(n) if n==y =>println!("Matched,n=={}",n),
		_=>println!("Default case,x={:?}",x),
	}
	println!("at the end:x={:?},y={}",x,y);
}

这里新建一个n,然后match guardn==y,这里的y是外部的y,并不是新建的 也可以在match guard种使用或|来指定多个模式,同时匹配守卫的条件会作用于所有的模式

let x = 4;
let y = false;
match x{
	4|5|6 if y => println!("yes"),
	_ => println!("no"),
}

|的优先级高于match guard,在满足 x 属于 4 | 5 | 6 后才会判断 y 是否为 true

@绑定

@at允许为一个字段绑定另一个变量 下面,希望测试Message::Hello的id字段是否位于3..=7内,同时也将其值绑定到id_variable变量中以便此分支中相关的代码可以使用,也可以将id_variable命名为id,和字段相同

enum Message{
	Hello{id:i32},
}

let msg = Message:Hello{id:5};

match msg{
	Message::Hello{id:id_variable @ 3..=7}=>{
		println!("Found an id in range:{}",id_variable)
	},
	Message::Hello{id:10..=12}=>{
		println!("Found an id in another range")
	},
	Message::Hello{id}=>{
		println!("Found some other id:{}",id)
	},
}

上例会打印出 Found an id in range: 5 分支1执行;分支2只在模式中指定了一个范围,但是这个模式的代码,并不知情也不能使用id的值 当及想要限定分支的范围,又想要使用分支的变量时,就可以用@来绑定到一个新的变量上,实现想要的功能

@前绑定后解构

使用@可以在绑定新变量的同时,对目标进行解构:

#[derive(Debug)]
struct Point{
	x:i32,
	y:i32,
}
fn main(){

	let p @ Point{x:px,y:py}=Point{x:10,y:23};
	if let p @ Point{x:10,y}=point{
		println!("x is 10 and y is {} in {:?}",y,p);
	}else{
		println!("x was not 10:(");
	}
}