RUST 0x05 Enum
1 定义一个Enum
如:
enum IpAddrKind {
V4,
V6,
}
enum的值只能是它的变量中的一个。
Enum Values
可以像这样创建实例:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
enum里的变量是在其namespace下的,所以要用::
。这时IpAddrKind::V4
和IpAddrKind::V6
是同一种类型——IpAddrKind
,所以可以像这样:
fn route(ip_kind: IpAddrKind) {
route(IpAddrKind::V4);
route(IpAddrKind::V6);
可以这样将enum和struct组合使用:
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
也可以直接在enum里使V4
和V6
变量与String
值相联系:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
enum比struct的一个优越之处在于:每一个变量都可以拥有不同类型的、不同数量的相关数据,比如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
除了string、数字类型,我们当然也可以在enum里放struct甚至另一个enum,比如:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
这个enum中含有四个不同类型的变量:
-
Quit
没有相关数据。 -
Move
中有一个anonymous struct。 -
Wtite
中有一个String
。 -
ChangeColor
中有三个i32
值
与struct类似,enum也可以在impl
中定义一些method,如:
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Write(String::from("hello"));
m.call();
Option
Enum与它相比于Null Values的优势
Rust中没有Null Values,但是它有一种enum,可以encode变量现在present或absent的概念,如:
enum Option<T> {
Some(T),
None,
}
Option<T>
enum仍然是一种regular enum,Some(T)
和None
仍然是Option<T>
类型的变量,但是如果要使用它们,可以直接使用,无需加Option::
前缀。
-
<T>
代表的是泛型(generic type),这意味着Some
可以存储任何类型的数据。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
如果我们要使用的不是Some
而是None
,我们需要告诉Rust Option<T>
的类型,否则编译器就不知道Some
应该存储的数据类型。
那么Option
到底哪里比Null Value好了呢?——编译器不会让我们像使用一个绝对有效值一样使用一个Option<T>
值。如下面的代码将会抛出一个CE,因为它试图把一个i8
加到一个Option<i8>
上:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
也就是说,如果我们需要进行T
能进行的操作,我们需要先把Option<T>
转变为T
。这样就可以防止一种错误:误以为某一个是null的值不是null。
2 The match
Control Flow Operator
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,
}
}
-
=>
→ separates the pattern and the code to run.
注意一下这里用的仍然是,
。
Patterns that Bind to Values
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
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(UsState::Alaska)
时,UsState::Alaska
会被绑定(bind)到state
上。
Matching with Option<T>
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);
Matches Are Exhaustive
在match的时候,必须将enum中每一个变量对应的情况都写出来,尤其是在Option<T>
的情况下。
The _
Placeholder
如果我们不想把所有可能的值都列出来,我们可以用_
,比如:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
(u8
可以有0~255之间的值)。
3 Concise Control Flow with if let
如果要实现这样的功能:
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
我们可以用if let
来减少码量:
if let Some(3) = some_u8_value {
println!("three");
}
if let
的工作原理和match
一样,但是码量更少。
if let
可以像if
一样地加else
,比如要实现这样的功能:
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
可以这样写:
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
小结
- enum是一个enumerated values的集合。
-
Option<T>
可以用来防止错误。 - 如果想要获得enum内的值,可以用
match
或if let
。
参考
The Rust Programming Language by Steve Klabnik and Carol Nichols, with contributions from the Rust Community : https://doc.rust-lang.org/book/