Rust 枚举与模式匹配
定义枚举
枚举值
定义枚举类型:
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; } }
下一章:Rust 错误处理
Rust的错误分两种: 可恢复错误不可恢复错误 rust提供了可恢复错误的类型Result< T,E >,与不可恢复错误时终止运行的panic!宏。 不可恢复错误与panic! 程序会在panic!宏执行时打 ...