Rust中枚舉與模式匹配的使用
在本文中,首先,我們將定義和使用枚舉。接下來,我們將探討一個(gè)特別有用的枚舉,稱為 Option。然后,我們將了解 match 表達(dá)式中的模式匹配。最后,我們將介紹 if let 構(gòu)造。
定義枚舉(Enum)
結(jié)構(gòu)體提供了一種將相關(guān)字段和數(shù)據(jù)分組在一起的方法,而枚舉則提供了一種說明一個(gè)值是一組可能值中的一個(gè)的方法。
任何 IP 地址都可以是 IPv4 或者 IPv6,但不能同時(shí)是這兩個(gè)地址。IP 地址的這個(gè)屬性使得枚舉數(shù)據(jù)結(jié)構(gòu)非常合適,因?yàn)槊杜e值只能是它的一個(gè)變體。
定義 IpAddrKind 枚舉:
enum IpAddrKind { V4, V6, }
IpAddrKind 是一個(gè)自定義數(shù)據(jù)類型。
枚舉變量
我們可以像這樣創(chuàng)建 IpAddrKind 的兩個(gè)變體的實(shí)例:
let four = IpAddrKind::V4; let six = IpAddrKind::V6;
注意,枚舉的變體位于其標(biāo)識符下的命名空間中,我們使用雙冒號分隔兩者。這是有用的,因?yàn)楝F(xiàn)在兩個(gè)值 IpAddrKind::V4 和 IpAddrKind::V6 都是同一類型:IpAddrKind。
我們還可以定義一個(gè)接受任意 IpAddrKind 的函數(shù):
fn route(ip_kind: IpAddrKind) {}
我們可以用任意一個(gè)變量調(diào)用這個(gè)函數(shù):
route(IpAddrKind::V4); route(IpAddrKind::V6);
枚舉可以作為結(jié)構(gòu)體的字段:
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"), };
但是,僅使用枚舉表示相同的概念更為簡潔:我們可以將數(shù)據(jù)直接放入每個(gè)枚舉變體中,而不是在結(jié)構(gòu)體中使用枚舉。這個(gè)枚舉的新定義表明,V4 和 V6 的實(shí)例將具有相關(guān)的 String 值:
enum IpAddr { V4(String), V6(String), } let home = IpAddr::V4(String::from("127.0.0.1")); let loopback = IpAddr::V6(String::from("::1"));
我們直接將數(shù)據(jù)附加到枚舉的每個(gè)變體上,因此不需要額外的結(jié)構(gòu)體。我們定義的每個(gè)枚舉變體的名稱也成為構(gòu)造枚舉實(shí)例的函數(shù)。也就是說,IpAddr::V4() 是一個(gè)函數(shù)調(diào)用,它接受一個(gè) String 參數(shù)并返回一個(gè) IpAddr 類型的實(shí)例。
使用 enum 而不是 struct 還有另一個(gè)好處:每個(gè)變量可以有不同的關(guān)聯(lián)數(shù)據(jù)類型和數(shù)量。如果我們想要將 V4 地址存儲為四個(gè) u8 值,但仍然將 V6 地址表示為一個(gè) String 值,那么我們將無法使用結(jié)構(gòu)體。枚舉可以輕松處理這種情況:
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
讓我們來看看標(biāo)準(zhǔn)庫是如何定義 IpAddr 的:兩個(gè)不同結(jié)構(gòu)體的形式將地址數(shù)據(jù)嵌入到變量中,每個(gè)變量的定義不同。
struct Ipv4Addr { // --snip-- } struct Ipv6Addr { // --snip-- } enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }
注意,即使標(biāo)準(zhǔn)庫包含了 IpAddr 的定義,我們?nèi)匀豢梢詣?chuàng)建和使用我們自己的定義而不會產(chǎn)生沖突,因?yàn)槲覀儧]有將標(biāo)準(zhǔn)庫的定義引入我們的作用域。
我們也可以使用 impl 在枚舉上定義方法:
impl Message { fn call(&self) { // method body would be defined here } } let m = Message::Write(String::from("hello")); m.call();
方法的主體會使用 self 來獲取我們調(diào)用該方法的值。在這個(gè)例子中,我們創(chuàng)建了一個(gè)變量 m,它的值是 Message::Write(String::from(“hello”)),這就是 m.call() 運(yùn)行時(shí)調(diào)用方法體中的 self 的值。
Option 枚舉及其相對于 NULL 的優(yōu)勢
Option 是標(biāo)準(zhǔn)庫定義的另一個(gè)枚舉。Option 類型編碼了一種非常常見的場景,在這種場景中,值可以是什么東西,也可以是空(什么都沒有)。
Rust沒有許多其他語言所具有的 null 特性。null 是一個(gè)表示沒有值的值。在帶有 null 的語言中,變量總是處于兩種狀態(tài)之一:null 或非 null。
空值的問題是,如果嘗試將空值用作非空值,將得到某種錯(cuò)誤。然而,null 試圖表達(dá)的概念仍然是有用的:null 是由于某種原因當(dāng)前無效或不存在的值。
問題不在于概念,而在于具體的實(shí)現(xiàn)。因此,Rust 沒有空值,但它有一個(gè)枚舉,可以編碼值存在或不存在的概念。該 enum 為 Option<T>,由標(biāo)準(zhǔn)庫定義如下:
enum Option<T> { None, Some(T), }
可以直接使用 Some 和 None,而不使用 Option:: 前綴。Some(T) 和 None 是 Option<T> 類型的變體。
<T> 語法是 Rust 的一個(gè)我們還沒有討論的特性。它是一個(gè)泛型類型參數(shù),意味著 Option 枚舉的某些變體可以保存任何類型的數(shù)據(jù)。
示例:
let some_number = Some(5); let some_char = Some('e'); let absent_number: Option<i32> = None;
some_number 的類型為 Option<i32>,some_char 的類型是 Option<char>。對于 None,Rust 要求必須提供具體的 Option 類型。
當(dāng)我們有一個(gè) None 值時(shí),在某種意義上它和 null 的意思是一樣的:我們沒有一個(gè)有效值。那么為什么 Option<T> 比 null 好呢?因?yàn)?Option<T> 和 T (T 可以是任何類型)是不同的類型。
例如,這段代碼無法編譯,因?yàn)樗噲D將 i8 添加到 Option<i8>:
let x: i8 = 5; let y: Option<i8> = Some(5); let sum = x + y;
Rust 不理解如何添加 i8 和 Option<i8>,因?yàn)樗鼈兪遣煌念愋汀?/p>
須先將 Option<T> 轉(zhuǎn)換為 T,然后才能對其執(zhí)行 T 操作。一般來說,這有助于抓住 null 最常見的問題之一:假設(shè)某些東西不是空的,而實(shí)際上是空的。為了擁有一個(gè)可能為空的值,必須顯式地將該值的類型設(shè)置為 Option<T>。然后,當(dāng)使用該值時(shí),需要顯式地處理該值為空的情況。只要值的類型不是 Option<T>,就可以放心地假設(shè)該值不為空。
這是 Rust 經(jīng)過深思熟慮的設(shè)計(jì)決策,目的是限制 null 的普遍性,提高 Rust 代碼的安全性。
match 和枚舉
match 表達(dá)式是一個(gè)控制流結(jié)構(gòu),當(dāng)與枚舉一起使用時(shí),它就是這樣做的:它將運(yùn)行不同的代碼,這取決于它擁有的枚舉的哪個(gè)變體,并且該代碼可以使用匹配值中的數(shù)據(jù)。
我們可以編寫一個(gè)函數(shù),它接受一枚未知的美國硬幣,并以與計(jì)數(shù)機(jī)類似的方式確定它是哪一枚硬幣,并返回其以美分為單位的值:
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, } }
當(dāng)匹配表達(dá)式執(zhí)行時(shí),它按順序?qū)⒔Y(jié)果值與每個(gè)模式進(jìn)行比較。如果模式匹配該值,則執(zhí)行與該模式關(guān)聯(lián)的代碼。如果該模式與值不匹配,則繼續(xù)執(zhí)行。
match 的的另一個(gè)有用特性是:它們可以綁定到與模式匹配的值部分。這就是從枚舉變量中提取值的方法。
作為一個(gè)例子,讓我們修改一個(gè)枚舉變量,使其包含數(shù)據(jù)。
#[derive(Debug)] // so we can inspect the state in a minute enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), }
在這段代碼的匹配表達(dá)式中,我們將一個(gè)名為 state 的變量添加到匹配變量 Coin::Quarter 值的模式中。當(dāng)一個(gè) Coin::Quarter 匹配時(shí),狀態(tài)變量將綁定到該 Quarter 的狀態(tài)值。然后我們可以在代碼中使用 state,如下所示:
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 } } }
如果我們調(diào)用 value_in_cents(Coin::Quarter(UsState::Alaska)),Coin 將是 Coin::Quarter(UsState::Alaska)。當(dāng)我們將該值與每個(gè)匹配進(jìn)行比較時(shí),在到達(dá) Coin::Quarter(state) 之前,它們都不匹配。此時(shí),州的綁定將是值 UsState::Alaska。然后我們可以使用 println! 打印該值。
與 Option<T> 匹配
我們還可以使用 match 來處理 Option<T>。
編寫一個(gè)函數(shù),它接受 Option<i32>,如果里面有一個(gè)值,則將該值加 1。如果里面沒有值,函數(shù)應(yīng)該返回 None 值,并且不嘗試執(zhí)行任何操作。
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);
match 應(yīng)該是詳盡的
match 中的模式必須涵蓋所有可能性。
考慮一下這個(gè)版本的 plus_one 函數(shù):
fn plus_one(x: Option<i32>) -> Option<i32> { match x { Some(i) => Some(i + 1), } }
報(bào)錯(cuò):error[E0004]: non-exhaustive patterns: `None` not covered。
我們沒有處理 None 的情況,所以無法編譯。
Rust 中的匹配是詳盡的:為了使代碼有效,我們必須窮盡每一種可能性。特別是在 Option<T> 的情況下,當(dāng) Rust 防止我們忘記顯式處理 None 情況時(shí),它保護(hù)我們避免在可能為 null 的情況下假設(shè)我們有一個(gè)值。
Catch-all 模式和 _ 占位符
使用枚舉,我們還可以對一些特定的值采取特殊的操作,但對所有其他值采取默認(rèn)操作。
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), other => move_player(other), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn move_player(num_spaces: u8) {}
最后一個(gè)模式 other 將匹配所有沒有明確列出的值,other 必須放在最后。
當(dāng)我們想要捕獲所有值,但又不想在捕獲所有值的模式中使用值時(shí)可以使用 _
。這是一個(gè)特殊的模式,它匹配任何值,并且不綁定到該值。這告訴 Rust 我們不打算使用這個(gè)值,所以 Rust 不會警告我們一個(gè)未使用的變量。
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {}
這個(gè)例子也滿足窮竭性要求,因?yàn)槲覀冿@式地忽略了所有的其他值。
還可以有另外一種寫法:
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}
使用 () 作為與 _
匹配時(shí)的動作。這將告訴 Rust:不使用任何不匹配先前模式的值,并且不想在這種情況下運(yùn)行任何代碼。
使用 if let 和 let else 簡化控制流
if let 語法以一種更簡潔的方式來處理匹配一個(gè)模式的值,同時(shí)忽略其他模式。
let config_max = Some(3u8); match config_max { Some(max) => println!("The maximum is configured to be {max}"), _ => (), }
如果值是 Some,我們通過將值綁定到模式中的變量 max 來打印出 Some 變量中的值。不對 None 值做任何事情。
每次都要寫 _ => () 確實(shí)很煩,可以用 if let 語法進(jìn)行簡化:
let config_max = Some(3u8); if let Some(max) = config_max { println!("The maximum is configured to be {max}"); }
if let 的語法接受一個(gè)模式和一個(gè)用等號分隔的表達(dá)式。它的工作方式與匹配相同,將表達(dá)式提供給匹配,而模式是它的第一個(gè)臂。在本例中,模式是 Some(max),并且 max 綁定到 Some 內(nèi)部的值。if let 塊中的代碼僅在值與模式匹配時(shí)運(yùn)行。
使用 if let 意味著更少的輸入、更少的縮進(jìn)和更少的樣板代碼。但是,失去了 match 強(qiáng)制執(zhí)行的詳盡檢查。
換句話說,可以將 if let 視為匹配的語法糖,當(dāng)值匹配一個(gè)模式時(shí)運(yùn)行代碼,然后忽略所有其他值。
我們可以在 if 語句中包含 else 語句,該代碼塊相當(dāng)于 if let 和 else。
match 寫法:
let mut count = 0; match coin { Coin::Quarter(state) => println!("State quarter from {state:?}!"), _ => count += 1, }
if let…else 寫法:
let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {state:?}!"); } else { count += 1; }
let else 的高階用法
let else 語法在左側(cè)接受一個(gè)模式,在右側(cè)接受一個(gè)表達(dá)式(變量),這與 if let 非常相似,但它沒有 if 分支,只有 else 分支。如果模式匹配,它將在外部作用域中綁定來自模式的值。如果模式不匹配,程序?qū)⑦M(jìn)入 else。
一種常見的模式是當(dāng)值存在時(shí)執(zhí)行一些計(jì)算,否則返回默認(rèn)值。
fn describe_state_quarter(coin: Coin) -> Option<String> { if let Coin::Quarter(state) = coin { if state.existed_in(1900) { Some(format!("{state:?} is pretty old, for America!")) } else { Some(format!("{state:?} is relatively new.")) } } else { None } }
我們還可以利用表達(dá)式生成的值來從 if let 中生成狀態(tài)或提前返回。
用 if let 來寫:
fn describe_state_quarter(coin: Coin) -> Option<String> { let state = if let Coin::Quarter(state) = coin { state } else { return None; }; if state.existed_in(1900) { Some(format!("{state:?} is pretty old, for America!")) } else { Some(format!("{state:?} is relatively new.")) } }
用 let else 來寫,會更簡單:
fn describe_state_quarter(coin: Coin) -> Option<String> { let Coin::Quarter(state) = coin else { return None; }; if state.existed_in(1900) { Some(format!("{state:?} is pretty old, for America!")) } else { Some(format!("{state:?} is relatively new.")) } }
到此這篇關(guān)于Rust中枚舉與模式匹配的使用的文章就介紹到這了,更多相關(guān)Rust 枚舉與模式匹配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust使用csv crate構(gòu)建CSV文件讀取器的全過程
這篇文章主要學(xué)習(xí)如何基于Rust使用csv這個(gè)crate構(gòu)建一個(gè)CSV文件讀取器的過程,學(xué)習(xí)了csv相關(guān)的用法以及一些往期學(xué)過的crate的復(fù)習(xí),兼顧了實(shí)用性和Rust的學(xué)習(xí),需要的朋友可以參考下2024-05-05使用Rust語言編寫一個(gè)ChatGPT桌面應(yīng)用示例詳解
這篇文章主要介紹了如何用Rust編寫一個(gè)ChatGPT桌面應(yīng)用,文中有詳細(xì)的流程介紹,對大家的學(xué)習(xí)或工作有意一定的幫助,需要的朋友可以參考下2023-05-05rust中間件actix_web在項(xiàng)目中的使用實(shí)戰(zhàn)
這篇文章主要介紹了rust中間件在項(xiàng)目中的使用實(shí)戰(zhàn),包括自定義中間件,日志中間件,Default?headers,用戶會話,錯(cuò)誤處理的用法實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01