Rust 學習筆記:枚舉與模式匹配

Rust 學習筆記:枚舉與模式匹配

  • Rust 學習筆記:枚舉與模式匹配
    • 定義枚舉(Enum)
    • 枚舉變量
    • Option 枚舉及其相對于 NULL 的優勢
    • match 和枚舉
    • 與 Option\<T\> 匹配
    • match 應該是詳盡的
    • Catch-all 模式和 _ 占位符
    • 使用 if let 和 let else 簡化控制流
    • let else 的高階用法

Rust 學習筆記:枚舉與模式匹配

在本文中,首先,我們將定義和使用枚舉。接下來,我們將探討一個特別有用的枚舉,稱為 Option。然后,我們將了解 match 表達式中的模式匹配。最后,我們將介紹 if let 構造。

定義枚舉(Enum)

結構體提供了一種將相關字段和數據分組在一起的方法,而枚舉則提供了一種說明一個值是一組可能值中的一個的方法。

任何 IP 地址都可以是 IPv4 或者 IPv6,但不能同時是這兩個地址。IP 地址的這個屬性使得枚舉數據結構非常合適,因為枚舉值只能是它的一個變體。

定義 IpAddrKind 枚舉:

enum IpAddrKind {V4,V6,
}

IpAddrKind 是一個自定義數據類型。

枚舉變量

我們可以像這樣創建 IpAddrKind 的兩個變體的實例:

    let four = IpAddrKind::V4;let six = IpAddrKind::V6;

注意,枚舉的變體位于其標識符下的命名空間中,我們使用雙冒號分隔兩者。這是有用的,因為現在兩個值 IpAddrKind::V4 和 IpAddrKind::V6 都是同一類型:IpAddrKind。

我們還可以定義一個接受任意 IpAddrKind 的函數:

fn route(ip_kind: IpAddrKind) {}

我們可以用任意一個變量調用這個函數:

    route(IpAddrKind::V4);route(IpAddrKind::V6);

枚舉可以作為結構體的字段:

    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"),};

但是,僅使用枚舉表示相同的概念更為簡潔:我們可以將數據直接放入每個枚舉變體中,而不是在結構體中使用枚舉。這個枚舉的新定義表明,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"));

我們直接將數據附加到枚舉的每個變體上,因此不需要額外的結構體。我們定義的每個枚舉變體的名稱也成為構造枚舉實例的函數。也就是說,IpAddr::V4() 是一個函數調用,它接受一個 String 參數并返回一個 IpAddr 類型的實例。

使用 enum 而不是 struct 還有另一個好處:每個變量可以有不同的關聯數據類型和數量。如果我們想要將 V4 地址存儲為四個 u8 值,但仍然將 V6 地址表示為一個 String 值,那么我們將無法使用結構體。枚舉可以輕松處理這種情況:

    enum IpAddr {V4(u8, u8, u8, u8),V6(String),}let home = IpAddr::V4(127, 0, 0, 1);let loopback = IpAddr::V6(String::from("::1"));

讓我們來看看標準庫是如何定義 IpAddr 的:兩個不同結構體的形式將地址數據嵌入到變量中,每個變量的定義不同。

struct Ipv4Addr {// --snip--
}struct Ipv6Addr {// --snip--
}enum IpAddr {V4(Ipv4Addr),V6(Ipv6Addr),
}

注意,即使標準庫包含了 IpAddr 的定義,我們仍然可以創建和使用我們自己的定義而不會產生沖突,因為我們沒有將標準庫的定義引入我們的作用域。

我們也可以使用 impl 在枚舉上定義方法:

    impl Message {fn call(&self) {// method body would be defined here}}let m = Message::Write(String::from("hello"));m.call();

方法的主體會使用 self 來獲取我們調用該方法的值。在這個例子中,我們創建了一個變量 m,它的值是 Message::Write(String::from(“hello”)),這就是 m.call() 運行時調用方法體中的 self 的值。

Option 枚舉及其相對于 NULL 的優勢

Option 是標準庫定義的另一個枚舉。Option 類型編碼了一種非常常見的場景,在這種場景中,值可以是什么東西,也可以是空(什么都沒有)。

Rust沒有許多其他語言所具有的 null 特性。null 是一個表示沒有值的值。在帶有 null 的語言中,變量總是處于兩種狀態之一:null 或非 null。

空值的問題是,如果嘗試將空值用作非空值,將得到某種錯誤。然而,null 試圖表達的概念仍然是有用的:null 是由于某種原因當前無效或不存在的值。

問題不在于概念,而在于具體的實現。因此,Rust 沒有空值,但它有一個枚舉,可以編碼值存在或不存在的概念。該 enum 為 Option<T>,由標準庫定義如下:

enum Option<T> {None,Some(T),
}

可以直接使用 Some 和 None,而不使用 Option:: 前綴。Some(T) 和 None 是 Option<T> 類型的變體。

<T> 語法是 Rust 的一個我們還沒有討論的特性。它是一個泛型類型參數,意味著 Option 枚舉的某些變體可以保存任何類型的數據。

示例:

    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 類型。

當我們有一個 None 值時,在某種意義上它和 null 的意思是一樣的:我們沒有一個有效值。那么為什么 Option<T> 比 null 好呢?因為 Option<T> 和 T (T 可以是任何類型)是不同的類型。

例如,這段代碼無法編譯,因為它試圖將 i8 添加到 Option<i8>:

    let x: i8 = 5;let y: Option<i8> = Some(5);let sum = x + y;

Rust 不理解如何添加 i8 和 Option<i8>,因為它們是不同的類型。

須先將 Option<T> 轉換為 T,然后才能對其執行 T 操作。一般來說,這有助于抓住 null 最常見的問題之一:假設某些東西不是空的,而實際上是空的。為了擁有一個可能為空的值,必須顯式地將該值的類型設置為 Option<T>。然后,當使用該值時,需要顯式地處理該值為空的情況。只要值的類型不是 Option<T>,就可以放心地假設該值不為空。

這是 Rust 經過深思熟慮的設計決策,目的是限制 null 的普遍性,提高 Rust 代碼的安全性。

match 和枚舉

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,}
}

當匹配表達式執行時,它按順序將結果值與每個模式進行比較。如果模式匹配該值,則執行與該模式關聯的代碼。如果該模式與值不匹配,則繼續執行。

match 的的另一個有用特性是:它們可以綁定到與模式匹配的值部分。這就是從枚舉變量中提取值的方法。

作為一個例子,讓我們修改一個枚舉變量,使其包含數據。

#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {Alabama,Alaska,// --snip--
}enum Coin {Penny,Nickel,Dime,Quarter(UsState),
}

在這段代碼的匹配表達式中,我們將一個名為 state 的變量添加到匹配變量 Coin::Quarter 值的模式中。當一個 Coin::Quarter 匹配時,狀態變量將綁定到該 Quarter 的狀態值。然后我們可以在代碼中使用 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}}
}

如果我們調用 value_in_cents(Coin::Quarter(UsState::Alaska)),Coin 將是 Coin::Quarter(UsState::Alaska)。當我們將該值與每個匹配進行比較時,在到達 Coin::Quarter(state) 之前,它們都不匹配。此時,州的綁定將是值 UsState::Alaska。然后我們可以使用 println! 打印該值。

與 Option<T> 匹配

我們還可以使用 match 來處理 Option<T>。

編寫一個函數,它接受 Option<i32>,如果里面有一個值,則將該值加 1。如果里面沒有值,函數應該返回 None 值,并且不嘗試執行任何操作。

    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 應該是詳盡的

match 中的模式必須涵蓋所有可能性。

考慮一下這個版本的 plus_one 函數:

    fn plus_one(x: Option<i32>) -> Option<i32> {match x {Some(i) => Some(i + 1),}}

報錯:error[E0004]: non-exhaustive patterns: `None` not covered。

我們沒有處理 None 的情況,所以無法編譯。

Rust 中的匹配是詳盡的:為了使代碼有效,我們必須窮盡每一種可能性。特別是在 Option<T> 的情況下,當 Rust 防止我們忘記顯式處理 None 情況時,它保護我們避免在可能為 null 的情況下假設我們有一個值。

Catch-all 模式和 _ 占位符

使用枚舉,我們還可以對一些特定的值采取特殊的操作,但對所有其他值采取默認操作。

    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) {}

最后一個模式 other 將匹配所有沒有明確列出的值,other 必須放在最后。

當我們想要捕獲所有值,但又不想在捕獲所有值的模式中使用值時可以使用 _。這是一個特殊的模式,它匹配任何值,并且不綁定到該值。這告訴 Rust 我們不打算使用這個值,所以 Rust 不會警告我們一個未使用的變量。

    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() {}

這個例子也滿足窮竭性要求,因為我們顯式地忽略了所有的其他值。

還可以有另外一種寫法:

    let dice_roll = 9;match dice_roll {3 => add_fancy_hat(),7 => remove_fancy_hat(),_ => (),}fn add_fancy_hat() {}fn remove_fancy_hat() {}

使用 () 作為與 _ 匹配時的動作。這將告訴 Rust:不使用任何不匹配先前模式的值,并且不想在這種情況下運行任何代碼。

使用 if let 和 let else 簡化控制流

if let 語法以一種更簡潔的方式來處理匹配一個模式的值,同時忽略其他模式。

    let config_max = Some(3u8);match config_max {Some(max) => println!("The maximum is configured to be {max}"),_ => (),}

如果值是 Some,我們通過將值綁定到模式中的變量 max 來打印出 Some 變量中的值。不對 None 值做任何事情。

每次都要寫 _ => () 確實很煩,可以用 if let 語法進行簡化:

    let config_max = Some(3u8);if let Some(max) = config_max {println!("The maximum is configured to be {max}");}

if let 的語法接受一個模式和一個用等號分隔的表達式。它的工作方式與匹配相同,將表達式提供給匹配,而模式是它的第一個臂。在本例中,模式是 Some(max),并且 max 綁定到 Some 內部的值。if let 塊中的代碼僅在值與模式匹配時運行。

使用 if let 意味著更少的輸入、更少的縮進和更少的樣板代碼。但是,失去了 match 強制執行的詳盡檢查。

換句話說,可以將 if let 視為匹配的語法糖,當值匹配一個模式時運行代碼,然后忽略所有其他值。

我們可以在 if 語句中包含 else 語句,該代碼塊相當于 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 語法在左側接受一個模式,在右側接受一個表達式(變量),這與 if let 非常相似,但它沒有 if 分支,只有 else 分支。如果模式匹配,它將在外部作用域中綁定來自模式的值。如果模式不匹配,程序將進入 else。

一種常見的模式是當值存在時執行一些計算,否則返回默認值。

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}
}

我們還可以利用表達式生成的值來從 if let 中生成狀態或提前返回。

用 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."))}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78909.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78909.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78909.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《WebGIS之Vue進階教程》(13)ref的實現

1 為什么需要ref 由于proxy只能代理引用類型數據(如: 對象, 數組, Set, Map...), 需要一種方式代理普通類型數據(String, Number, Boolean...) 設計ref主要是為了處理普通類型數據, 使普通類型數據也具有響應式 除此之外, 通過reactive代理的對象可能會出現響應丟失的情況. 使…

Redis 緩存并發問題深度解析:擊穿、雪崩與穿透防治指南

Redis-緩存并發 引言&#xff1a;緩存&#xff0c;高性能架構的基石與并發挑戰一、 緩存擊穿&#xff1a;熱點 Key 失效引發的“單點風暴”1.1 什么是緩存擊穿&#xff1f;1.2 緩存擊穿的風險1.3 緩存擊穿的解決方案1.3.1 互斥鎖&#xff08;Mutex Lock&#xff09;/ 分布式鎖 …

Python 數據智能實戰 (4):智能用戶分群 - 融合行為

寫在前面 —— 超越 RFM 標簽,結合用戶行為與 LLM 文本洞察,實現更精準、更立體的客戶細分 歡迎回來!在前面的學習中,我們已經為 Python 數據智能工具箱添置了與大語言模型 (LLM) 交互的能力,特別是掌握了如何利用 LLM 將非結構化的文本信息轉化為包含深層語義的數值向量…

FreeMarker語法深度解析與Node.js集成實踐指南

一、FreeMarker核心語法體系 1.1 基礎模板結構 <#-- 注釋語法 --> ${expression} <#-- 輸出表達式 --> <#directive paramvalue> <#-- 指令語法 -->1.2 數據類型處理 標量類型深度處理&#xff1a; <#assign num 123.45?floor> <#--…

【計算機視覺】目標檢測:深度解析YOLOv5:下一代實時目標檢測框架實戰指南

深度解析YOLOv5&#xff1a;下一代實時目標檢測框架實戰指南 技術演進與架構設計YOLO系列發展脈絡YOLOv5核心架構1. 骨干網絡&#xff08;Backbone&#xff09;2. 特征融合&#xff08;Neck&#xff09;3. 檢測頭&#xff08;Head&#xff09; 環境配置與快速開始硬件要求建議詳…

STM32 定時器TIM

定時器基礎知識 定時器就是用來定時的機器&#xff0c;是存在于STM32單片機中的一個外設。STM32總共有8個定時器&#xff0c;分別是2個高級定時器(TIM1、TIM8)&#xff0c;4個通用定時器(TIM2、TIM3、TIM4、TIM5)和2個基本定時器(TIM6、TIM7)&#xff0c;如下圖所示: STM32F1…

OpenObserve API Usage Guide for Log Management

OpenObserve API Usage Guide for Audit Log Management 1. 概述 1.1 目標 本文檔旨在詳細介紹 OpenObserve 的 API 使用方法&#xff0c;幫助用戶通過 API 實現日志管理功能&#xff0c;包括日志攝入、查詢、模糊匹配&#xff08;類似 SQL 的 LIKE&#xff09;、stream 管理…

消防崗位技能競賽流程方案策劃

一、比賽目的&#xff1a; 為大力倡導“11.9”全國消防安全活動月&#xff0c;緊緊圍繞“人人參與消防&#xff0c;共創平安和諧”的活動主題&#xff0c;結合公司實際情況&#xff0c;特開展一次消防技能競賽活動。開展一場比思想、比工作作風、比消防業務技能、比業余文化生…

DAY9-USF4.0技術文檔筆記

目錄 1.概述 2.參考協議標準 3.術語與定義 4.引言 5.UFS架構 6.UFS電氣特性&#xff1a;時鐘、復位、信號與電源 7.復位、加電升壓和斷電降壓 8. M-PHY 9.UniPro 10.UTP 11.SCSI 12.UFS安全 13.UFS功能描述 14.描述符、標志與屬性 15.UFS機械標準 SCSI 查詢命令 1.重要產品…

安裝kubernetes 1.33版本

一、環境準備 1、內核升級 #升級內核&#xff1a; yum -y install kernel-ml-5.10.3-1.el7.elrepo.x86_64.rpm kernel-ml-devel-5.10.3-1.el7.elrepo.x86_64.rpm# 查詢可用內核版本 # awk -F\ $1"menuentry " {print i " : " $2} /etc/grub2.cfg# 調整默…

【IPMV】圖像處理與機器視覺:Lec8 Image Pyramid 圖像金字塔

【IPMV】圖像處理與機器視覺 本系列為2025年同濟大學自動化專業**圖像處理與機器視覺**課程筆記 Lecturer: Rui Fan、Yanchao Dong Lec0 Course Description Lec3 Perspective Transformation Lec7 Image Filtering Lec8 Image Pyramid 持續更新中 文章目錄 【IPMV】圖像處…

產品經理.產品設計.產品設計工具

一、 產品經理常用工具 1. 業務流程圖---系統流程圖 業務流程圖&#xff0c;面向用戶調研&#xff0c;描述業務的流轉和數據的處理要求&#xff0c;跟用戶和業務方確認&#xff1b;---業務角色的泳道流程圖。 系統流程圖&#xff0c;面向產品需求設計&#xff0c; prd系描述各…

6軸、智能、低功耗慣性測量單元BMI270及其OIS接口

BOSCH慣性傳感器IMUs 芯片代碼 通過00寄存器讀回的芯片編碼可以判斷芯片型號,BMI270為(0x24) &#xff0c;如不是該值&#xff0c;則說明不是BMI270。 型號芯片代碼BMI085CHIP_ID ( 0x1F)BMI088CHIP_ID ( 0x1E)BMI160CHIP_ID (0xD1)BMI270CHIP_ID (0x24)BMI323CHIP_ID (0x004…

【文獻速遞】鄰位連接技術(PLA)在細胞器相互作用中的應用

在神經科學研究領域&#xff0c;細胞死亡機制一直是關注的重點&#xff0c;尤其是與神經退行性疾病相關的細胞死亡形式。荷蘭格羅寧根大學的研究人員在2025年發表了“Regulation of calcium signaling prevents neuronal death mediated by NIST DEP in xenoferroptotic cell d…

六.割草機技術總結--6.RTK定位精度分析

六.割草機技術總結–6.RTK定位精度分析 6.1 1cm+1ppm 中的ppm是什么意思? 精度 RTK 位置精度(在 RTK 時)1 cm + 1 ppm ( 水 平 ) 1 . 5 cm + 1 ppm ( 垂 直 ),其中的ppm是什么意思? 在RTK(實時動態定位)技術中,ppm表示 Parts Per Million(百萬分之一),是一種與距離…

MCP的基礎知識

一、了解MCP的基礎知識 1.函數調用Function Calling Function Calling是openai在2023年推出的一個非常重要的概念&#xff1a;Function Calling&#xff08;函數調用&#xff09;本質上就是提供了大模型與外部系統的交互能力&#xff0c;類似于給大模型安裝了一個“外掛工具箱…

量化交易之數學與統計學基礎2.4——線性代數與矩陣運算 | 矩陣分解

量化交易之數學與統計學基礎2.4——線性代數與矩陣運算 | 矩陣分解 第二部分&#xff1a;線性代數與矩陣運算 第4節&#xff1a;矩陣分解&#xff1a;奇異值分解&#xff08;SVD&#xff09;在數據壓縮和風險分解的應用 一、奇異值分解&#xff08;SVD&#xff09;基礎&#xf…

極簡主義在 UI 設計中的應用與實踐:打造簡潔高效界面

極簡主義理念&#xff1a;簡潔不簡單? 極簡主義起源于 20 世紀初的包豪斯運動&#xff0c;它不僅是一種設計風格&#xff0c;更代表著一種生活態度與價值觀。其核心理念 “少即是多”&#xff0c;并非簡單地削減元素&#xff0c;而是在精簡中追求極致&#xff0c;將設計簡化到…

2025年“深圳杯”數學建模挑戰賽C題-分布式能源接入配電網的風險分析

布式能源接入配電網的風險分析 小驢數模 背景知識&#xff1a; 隨著我國雙碳目標的推進&#xff0c;可再生分布式能源在配電網中的大規模應用不可避免&#xff0c;這對傳統配電網運行提出挑戰。為了量化分析配電網中接入分布式能源的風險&#xff0c;需要對其進行建模與分析…

《解鎖LibTorch:開啟C++深度學習新征程》

《解鎖LibTorch:開啟C++深度學習新征程》 深度學習與 LibTorch 在當今數字化時代,深度學習已成為人工智能領域的核心驅動力,廣泛應用于計算機視覺、自然語言處理、語音識別等諸多領域,深刻改變著我們的生活和工作方式。它的發展歷程充滿了創新與突破,從最初的理論探索到如…