系列文章

  1. Rust-引用与借用
  2. Rust-枚举和Match匹配

上一篇文章里,我们探讨了Rust中的一个重要概念——引用和借用。在这篇文章中,我们将继续探讨本人在通过Rustling学习Rust中遇到的第二个问题——枚举(Enum)和模式匹配(Match)

枚举(Enum)

枚举通过关键字enum来声明,通过enum关键字我们可以声明一个指定的枚举类型
比如在Rust圣经中的一个例子:

1
2
3
4
5
6
enum PokerSuit{
Clubs,
Spades,
Diamonds,
Hearts,
}

这里我们声明了一个枚举类型PockerSuit,其中包含了4种枚举值:Clubs,Spades,Diamonds,Hearts。之后,我们便可以使用它们了,比如我们可以创建两个PockerSuit类型的成员实例:

1
2
3
4
5
6
7
8
9
10
11
12
    let heart = PokerSuit::Heart;
let clubs = PokerSuit::Clubs;
```

与其他语言不同,Rust可以将不同的数据类型直接关联到枚举成员上,例如我们给上文的扑克花色关联上具体的点数大小:
```rust
enum PokerSuit{
Clubs(u8),
Spades(u8),
Diamonds(u8),
Hearts(u8),
}

之后我们便可以给枚举类型的成员实例附上值了,这可比golang好用多了

1
2
let d12 = PockerSuit::Diamonds(12);
let s13 = PockerSuit::Spades(13)

当然,我们可不满足于此,对于同一枚举类型的不同的枚举成员,我们还可以给他关联上不同的数据类型:

1
2
3
4
5
6
enum Message{
Str(String),
Num(i32),
Ok(bool),
Tuple(i32,String,bool)//这里关联了个元组
}

我们甚至可以关联结构体或其他枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
//enum
enum PokerSuit{
Clubs(u8),
Spades(u8),
Diamonds(u8),
Hearts(u8),
}

enum Poker{
Suit(PokerSuit),
Flip(bool),
}
1
2
3
4
5
6
7
8
9
10
11
12
//struct
struct ArmedHelicopter{
Missile:String,
Type:String,
Pilot:String,
}

enum Gender{
Male(String),
FeMale(String),
Other(ArmedHelicopter),
}

真是强而有力,强而有力啊

Match——匹配

当我们成功声明了一个枚举类型之后可能会遇到一个问题:如何处理同一枚举类型下的不同枚举值?它们可能对应的是不同的数据类型
幸运的是,Rust给我们提供了模式匹配来方便我们对不同的枚举值进行处理,本文我们先探讨第一种模式匹配方法——match

如何使用

我们首先来看看match的通用形式

1
2
3
4
5
6
7
8
9
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}

要注意:

  • match的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同。
  • match的匹配必须要穷举出所有可能,这里使用_来匹配除了模式1模式2以外的其他所有情况
  • 匹配的模式可以使用|来表示逻辑运算中的,从而匹配多种模式,例如模式3 | 模式4 => 表达式foo,

注意!这里只能使用|,不能使用||&&!等逻辑运算符,match的模式匹配中每个匹配臂(arm)的模式必须是独立的,如果想要更复杂的匹配方式,需要添加匹配守卫(Pattern guard),来进一步改进匹配标准,关于匹配守卫,我们应该会在后面的文章中进一步探讨。

这里展示一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum PokerSuit{
Clubs,
Spades,
Diamonds,
Hearts,
}

fn show_poker_suit(suit:PokerSuit) -> char{
match suit {
PokerSuit::Clubs => '♣',
PokerSuit::Heart =>{
println!("♥!");
'♥';
},
PokerSuit::Spades =>'♠',
PokerSuit::Diamonds =>'♦',
}
}

使用match表达式赋值

match 本身也是一个表达式,因此可以用它来赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//偷了一个Rust圣经的例子
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);
}

因为这里匹配到_分支,所以将"::1" 赋值给了ip_str

模式绑定

终于来到了重头戏(这篇文章就是为了这个而生的)match的一个重要功能就是从模式中取出绑定的值,例如我们在枚举中举的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
enum Message{
Str(String),
Num(i32),
Ok(bool),
Tuple(i32,String,bool)
}

fn show_message(msg Message) -> String{
match msg {
Message::Str(strmsg) => strmsg,//这里将Str存储的字符串类型的值绑定在了变量strmsg上
Message::Num(nummsg) => nummsg.to_string(),
Message::Ok(okmsg) => format!("{}",okmsg),
Message::Tuple(x,y,z) => format!("({}, {}, {})",x,y,z)
}
}

穷尽匹配

简单来说,如果你在match中没有列出所有可能的匹配情况,cargo会报错 Cargo:写的什么垃圾代码,你给我去屎吧

_通配符

当我们不想在匹配时列出所有值的时候,可以使用 Rust 提供的一个特殊模式_通配符,比如我们的函数只关心PokerSuit中的红牌:

1
2
3
4
5
6
7
fn show_me_red(suit:PokerSuit) -> String{
match suit {
PokerSuit::Hearts => "I got ♥ !".to_string(),
PokerSuit::Diamonds => "I got ♦!".to_string(),
_ => "I got smoke :( ".to_string()//吸烟有害健康
}
}

当然,除了_,我们也可以用其他变量来承载其他情况:

1
2
3
4
5
6
7
fn show_me_red(suit:PokerSuit) -> String{
match suit {
PokerSuit::Hearts => "I got ♥ !".to_string(),
PokerSuit::Diamonds => "I got ♦!".to_string(),
other => format!("I got {:#?}",other),
}
}

总结

Rust提供了强大的枚举和match匹配来使得我们的代码更加简洁美观,让我们可以摆脱丑陋的if else,在后面的文章里(如果有的话),我们会进一步探讨其他的模式匹配方法,以及之前提到的匹配守卫(Pattern guard)。

部分内容引用自Rust语言圣经(Rust Course)