18 模式与模式匹配
模式是Rust中的特殊语法,用来匹配类型中的结构,无论类型复杂与否。模式由以下一些内容组合而成:
字面值\解构的数组、枚举、元组或者结构体\变量\通配符\占位符,这些部分描述了我们需要处理的数据形状。(匹配模式其实可以理解为匹配项,简单理解就是把两个项目拉起来比较,看等不等于,等于的话我们就执行一些代码,不等于的话执行另一些代码。这个点就跟条件语句外加等于号一样,只不过Rust中把它专门做了个匹配模式)
18.3 模式的全部语法
本节中,我们收集了模式中所有有效的语法,并将讨论为什么可能要使用每个语法
匹配字面值
let x =1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
Running `target\debug\model.exe`
one
匹配命名变量
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)
}
Running `target\debug\model.exe`
Matched,y = 5
at the end: x= Some(5), y = 10
match语句中引入了一个新的变量y,我们将它与x匹配,其实意思是将x的值赋值给这个内部变量y,这个y和match语法块外的那个y没有关系
多个模式
fn main() {
let x = 1;
match x {
1 | 2=> println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
通过… = 匹配值的范围
…= 允许我们匹配一个闭区间范围内的值
fn main() {
let x = 1;
match x {
1..=5 => println! ("one through five"),
_ => println!("something else"),
}
}
Running `target\debug\model.exe`
one through five
这种闭区间就比或好多了,尤其是数量比较大的时候,但是范围只允许用数字或者char的值。char和数字值是Rust仅有的可以判断范围是否为空的值
如下是一个使用char类型值范围的例子
fn main() {
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
}
early ASCII letter
结构并分解值
也可以使用模式来解构结构体、枚举、元组和引用,以便使用这些值的不同部分
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);
}
使用match语句将point值分为三种情况
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),
}
}
Running `target\debug\model.exe`
on the y axis at 7
解构枚举
enum Message {
Quit,
Move(i32,i32),
Write(String),
ChangeColor(i32,i32,i32),
}
fn main() {
let msg = Message::ChangeColor(0,160,255);
match msg {
Message::Quit => {
println!("the quit varient 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 meaasge:{}",text),
Message::ChangeColor(r,g,b) => {
println!("change the color to red {},green {},and blue {}",
r,
g,
b
)
}
}
}
上述代码发非常简单和易于理解,解构包含不同类型值成员的枚举
解构嵌套的结构体和枚举
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 {},saturation {},and value {}",
h,
s,
v
)
}
_=> ()
}
}
Running `target\debug\model.exe`
change the color to hue 0,saturation 160,and value 255
匹配嵌套的枚举
解构结构体和元组
甚至可以用复杂的方式来混合、匹配和嵌套结构模式
let ((feet, inches),Point{x,y}) =((3,10),Point{x:3,y:-10});
通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值
忽略模式中的值
有时候忽略模式中的一些值是有用的,比如match中最后捕获全部情况的分支实际上没有做任何事,但是它确实对所有剩余情况负责
使用 _ 忽略整个值
fn foo(_: i32,y:i32) {
println!("This code only uses the y parameter: {}",y)
}
fn main() {
foo(3, 4)
}
Running `target/debug/model`
This code only uses the y parameter: 4
大部分情况当你不再需要特定的参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变的特别有用,比如实现trait时,当你需要特定类型签名但是函数实现并不需要某个参数时,此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样
使用嵌套的 _ 忽略部分值
也可以在一个模式内部使用 _ 忽略部分值,例如,只需要测试部分值但在期望运行的代码中没有用到其他部分时
fn main() {
let mut setting_value = Some(5);
let mut new_setting_value = Some(10);
match (setting_value,new_setting_value) {
(Some(_),Some(_)) => {
println!("Can't overwrite an exiting customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}",setting_value)
}
Running `target/debug/model`
Can't overwrite an exiting customized value
setting is Some(5)
fn main() {
let numbers = (2,4,8,16,32);
match numbers {
(first,_,third,_,fifth) => {
println!("Some numbers: {},{},{}",first,third,fifth)
},
}
}
Running `target/debug/model`
Some numbers: 2,8,32
通过在名字前以一个下划线开头来忽略未使用的变量
如果你创建了一个变量却没有使用它,Rust编译器会给你一个警告,因为它可能是个Bug。但是你有时候却需要创建这样一个变量,比如正在设计原型或者刚开始一个新项目,这时可以使用下划线作为变量名的开头,这样rust编译器就不会警告了
fn main() {
let _x = 5;
let y = 10;
}
warning: unused variable: `y`
--> src/main.rs:5:7
|
5 | let y = 10;
| ^ help: if this is intentional, prefix it with an underscore: `_y`
|
= note: `#[warn(unused_variables)]` on by default
warning: `model` (bin "model") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.83s
Running `target/debug/model`
注意,只使用_和使用以下划线开头的名称有些微妙的不同:比如 _x 仍会将值绑定到变量,而 _ 则完全不会绑定
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{:?}",s);
}
我们会得到一个错误,因为s的值仍然会移动进_s,并阻止我们再次使用s。然而只使用下划线本身,并不会绑定值。上例不能编译,因为s没有被移动进 _
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{:?}",s);
}
found a string
Some("Hello!")
上面的代码能很好的运行,因为没有把s 绑定到任何变量,它没有被移动
用…忽略剩余值
… 有时候比 _ 更好用
fn main() {
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)
}
}
但是使用 … 必须是无歧义。如果期望匹配的和忽略的值是不明确的,Rust会报错
let numbers = (2,4,8,16,32);
match numbers {
(first,_,third,_,fifth) => {
println!("Some numbers: {},{},{}",first,third,fifth)
},
}
error: `..` can only be used once per tuple pattern
--> src/main.rs:7:20
|
7 | (..,second,..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
Rust不可能决定在元组中匹配second值之前应该忽略多少个值,以及在之后忽略多少个值
匹配守卫提供的额外条件
匹配守卫是一个指定于match分支模式之后的额外if条件,它也必须被满足才能选择此分支。匹配守卫用于表达式比单独的模式所能允许的更为复杂的情况
这个条件可以使用模式中创建的变量
fn main() {
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}",x),
Some(x ) => println!("{}",x),
None => (),
}
}
在模式中加入匹配守卫
Running `target/debug/model`
less than five: 4
Some(4)第一个匹配守卫为真,让我们再来看看匹配守卫为假的情况
fn main() {
let x = Some(10);
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)
}
也可以在匹配守卫中使用 |
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_=> println!("no")
}
}
@绑定
@允许我们在创建一个存放值的变量的同时测试点其值是否匹配模式
fn main() {
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)
},
}
}
Finished dev [unoptimized + debuginfo] target(s) in 0.72s
Runninng `target/debug/model`
found an id in range:5
通过在3…=7之前指定id_variable@,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围的模式
第二个分支只在模式中指定了一个范围,分支相关代码没有一包含id字段实际值的变量
最后一个分支指定了一个没有范围的变量,任何值都会匹配此分支
使用@可以在一个模式中同时测试和保存变量值
总结:模式是rust中一个很有用的功能,它帮助我们区分不同类型的数据
下一章,我们会讲一些Rust的一些高级功能
|