感谢大家关注!
▼▼▼
“关于Rust的前景、优势等不再赘述,这篇主要讲Rust的Enum特性以及pattern匹配。”
本系列将以“笔记”作为撰文形式,追求简洁明了,概念随时配合代码。如果行文过程中没有讲清楚的地方,欢迎后台私信。如有需要(请后台告诉我),Rust笔记系列更新完成后会再写一份详细的Rust图文教程。
01
—
总览
Rust的enums像是其他函数式语言的algebraic data types代数数据类型。
02
—
定义枚举类型和构造实例
/**
* 1. StrV4和StrV6是相同类型,可以作为同类型参数传入函数中
* 2. 直接在enum中把具体的值类型写到每个枚举类型里,写法更简洁(不必再增加一个结构体或其他代码)
* 3. 枚举类型里可以包含不同类型和数量的值
*/
#[derive(Debug)]
enum IpAddrKind {
StrV4(String),
StrV6(String),
VarV4(u8, u8, u8, u8),
}
fn basic_test() {
let home = IpAddrKind::StrV4(String::from("127.0.0.1"));
let loopback = IpAddrKind::StrV6(String::from("::1"));
let local = IpAddrKind::VarV4(192, 168, 0, 1);
route(home);
route(loopback);
route(local);
}
fn route(ip_kind: IpAddrKind) {
println!("The Ip address is: {:?}", ip_kind);
}
输出为:
The Ip address is: StrV4("127.0.0.1")
The Ip address is: StrV6("::1")
The Ip address is: VarV4(192, 168, 0, 1)
可见枚举类型的特点为:
不同的枚举值可以作为相同参数类型传入函数中。
可以直接在enum中把值类型和枚举类型结合起来,写法很简洁。
同一个enum里可以有不同的枚举类型,分别对应不同类型和数量的值。
枚举类型中可以关联的类型
枚举类型中可以不关联数据,也可以关联String和基本数据类型,还可以关联结构体和枚举。如果关联自身的话,需要使用关键字Box, Rc或&。
枚举类型同样可以使用impl定义method。
#[derive(Debug)]
enum Message {
Quit, // has no data associated with it at all.
Move { x: i32, y: i32}, // includes an anonymous struct inside it.
Write(String),// includes a single String.
ChangeColor(i32, i32, i32), // includes three i32 values.
Nested(Box<Message>), // insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Message` representable
}
impl Message {
fn previous_message(&self) {
println!("previous message is: {:?}", self);
}
}
fn message_test() {
let mut m = Message::Write(String::from("hello"));
m.previous_message();
m = Message::Move {x: 45, y: 33};
m.previous_message();
m = Message::ChangeColor(0, 196, 251);
m.previous_message();
m = Message::Quit;
m.previous_message();
m = Message::Nested(Box::from(m));
m.previous_message();
}
输出:
previous message is: Write("hello")
previous message is: Move { x: 45, y: 33 }
previous message is: ChangeColor(0, 196, 251)
previous message is: Quit
previous message is: Nested(Quit)
None和Option
Rust没有NULL类型。在需要表示无效值的时候用系统提供的Option枚举。
/// The `Option` type. See [the module level documentation](self) for more.
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[rustc_diagnostic_item = "option_type"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option<T> {
/// No value
#[lang = "None"]
#[stable(feature = "rust1", since = "1.0.0")]
None,
/// Some value `T`
#[lang = "Some"]
#[stable(feature = "rust1", since = "1.0.0")]
Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}
如果赋值为None的话,需要指明T的类型。用法如下:
fn option_test() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; // 需要指明类型
println!("some number: {:?}", some_number);
println!("some string: {:?}", some_string);
println!("absent number: {:?}", absent_number);
}
输出:
some number: Some(5)
some string: Some("a string")
absent number: None
这样的好处是在编译期即可防止误把未赋值的无效值参与运算,因为T
和Option<T>
不是同一类型:
let x: i8 = 5;
let mut y: Option<i8> = None;
let sum = x + y; // error: no implementation for `i8 + Option<i8>`
Rust编译器认为i8
和Optioin<i8>
不是同一类型,因此无法运算。
对其稍作修改:
let x: i8 = 5;
let mut y: Option<i8> = None;
y = Some(6);
let sum = x + y.expect("y is none");
println!("sum: {}", sum);
正常输出:
sum: 11假设我们忘记第三行的给y赋值,编译器会报错:
thread 'main' panicked at 'y is none'
以提醒我们y是none。
如果某个变量是通过Option声明的,表示它可能会存在为Null的情况,需要在使用时多加注意;反之,可以大胆放心地对它使用,因为它一定不会是Null。
03
—
match表达式
类似switch语句,从上到下逐个匹配。pattern可以是字面值,变量名或通配符等。匹配结果可以是单个返回值(返回值可以是任意类型),也可以是多条执行语句加一个返回值:
fn match_test() {
let coin = Coin::Penny;
println!("The value in coin: {}", value_in_cents(coin));
}
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,
}
}
输出:
Lucky penny
The value in coin: 1
pattern中绑定值
比如将上述代码中的Quarter
绑定上另外一个枚举类型UsState
,那么实际的匹配pattern会相应地变为Coin::Quarter(state)
,且传入参数变为Coin::Quarter(UsState::Alabama)
。这样一来,就可以在match表达式对应的pattern中拿到变量state
的值了。如下例:
fn match_test() {
let coin = Coin::Quarter(UsState::Alabama);
println!("The value in coin: {}", value_in_cents(coin));
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
输出:
State quarter from Alabama!
The value in coin: 25
pattern匹配
Option<T>
如下列:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn match_option() {
let five = Some(5);
println!("five plus one is: {:?}", plus_one(five));
println!("None plus one is: {:?}", plus_one(None));
}
输出:
five plus one is: Some(6)
None plus one is: None
如果我们在match中少写了某种情况,比如Coin::Nickel => 5,
或None => None,
,编译器会以报错提醒我们:
match coin {
^^^^ pattern `Nickel` not covered
match x {
^ pattern `None` not covered
当然,也有类似default
的“其他”pattern,即placeholder,用下划线表示:
fn match_placeholder() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
_ => println!("other"),
}
}
输出:
otherif let 匹配
只匹配一个pattern,其他与match一致。有点像if constexpr
,但是在比较时只有一个等号:
fn if_let_test() {
let coin = Coin::Quarter(UsState::Alaska);
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
}
输出:
State quarter from Alaska!以上是Rust的Ownership特性,配套代码见:
https://github.com/ds1231h/RustSimpleCodesrc/enums.rs
Rust的安装、编译与运行参加本系列第一篇:
想要学习最硬核的技术干货吗?
了解更多有趣的硬核干货
▼▼▼
撰文:沛沛




