定义枚举
枚举值
定义枚举类型:
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(IpAddrKind::V4);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {}
定义关联数据的枚举类型:
fn main() {
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
定义关联不同数据类型的枚举类型:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
这个枚举拥有4个内嵌了不同类型数据的变体:
- Quit没有关联任何数据
- Move包含了一个匿名结构体(不需要为结构体命名)
- Write包含了一个String(允许是任何类型,包括结构体,但不知道是否允许包含枚举类型)
- ChangeColor包含了3个i32值。(有点类似包含了元组)
枚举类型允许定义方法,语法与结构体相同。
fn main() {
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Write(String::from("hello"));
m.call();
}
Option枚举及其在空值处理方面的优势
空值常常引发BUG,rust引入Option< T >强制用户判断是否存在空值。
首先Option是个枚举类型,< T >是泛型,之后讨论,但是其作用就是表示Option枚举中的Some变体可以表示任意类型的数据。
enum Option<T> {
Some(T),
None,
}
当数据可能存在空值时,我们使用Option来表示,Some表示有值,None表示空值(仅仅是表示空值)。注意,使用None时一定要告知类型,因为None不像Some可以推导类型。
let some_number = Some(5);
let some_string = Some("a string");
let none_number:Option<i32>=None;
举个例子,如下x不可能存在空值,所以直接赋值,y可能存在空值,我们使用Option赋值。
let x: i8 = 5;
let y: Option<i8> = Some(5);
但是,x和y不能相加,因为他们拥有不同的类型。之所以这样,是rust需要强制你处理可能为空的值。
//错误代码
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
}
所以在rust中,不为Option的值必定不为空。有Option的值有可能为空,强制你进行处理,更加安全。
需要使用match来处理Option的some和none,先了解以下match。
控制流运算符match
match比较类似c语言中的switch,他会判断表达式进入第一个符合的模式,并且可以返回任意类型的值。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {}
match后面跟表达式,第一个分支用Coin::Penny作为模式,紧跟=>运算符用于将模式和代码区分开。这部分返回了值1。不同分支使用逗号隔开。模式匹配成功就执行代码,并退出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,
}
}
绑定值的模式
可以在模式中绑定值,这样就可以在逻辑代码中使用绑定的值。
#[derive(Debug)]
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
}
}
}
fn main() {
value_in_cents(Coin::Quarter(UsState::Alaska));
}
匹配Option< T >
我们可以通过match来处理Option< T >
fn main() {
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);
}
当调用plus_one(five)时,x为Some(5),匹配到Some(i),返回Some(6),这里的i绑定了x中5的值。
第二次调用plus_one(None),传递了None,匹配到None,返回了None。
(其实我觉得这个例子不好,match返回的值可以不是Option,这样才能体现对空值的处理。)
match匹配必须穷举所有可能
//错误代码
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
上面的错误代码忘记了处理None的情形,这是一个rust能轻松捕获的问题。这样的设计也是合理的、必要的。
_通配符
因为你必须处理所有的可能,所以你在匹配一个类型为u8的数据时需要匹配0到255。假设我们只关心1、3、5、7,那么我们可以通过_运算符来代表剩余的值。
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}
简单控制流if let
if let 能让我们通过不那么繁琐的语法结合使用if与let,并处理那些只用关心某一种匹配而忽略其他匹配的情况。(就是区别与match需要穷举所有可能)
fn main() {
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
}
可以在if let后搭配使用else:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
}