Rust 的列舉型別不僅能定義具名常數,還能包含不同型別的資料,為程式設計帶來更大的彈性。搭配模式匹配,更能安全地處理列舉的各種變體,避免執行階段錯誤,同時簡化程式碼邏輯。理解列舉的記憶體表示有助於撰寫更高效的程式碼,例如透過 #[repr] 屬性指定記憶體佈局。結合泛型,更能建立通用的資料結構,例如 Option 和 Result,或自定義的二元樹,展現 Rust 在資料結構設計上的彈性與效能優勢。
列舉與模式匹配
電腦科學中有許多概念看似複雜,但若以某種特定的角度切入,便能豁然開朗。列舉(Enums)便是其中之一。Rust 語言中的列舉不僅可以用來定義一組具名常數,更可以包含不同型別的資料,使得程式設計更加靈活且富有表達力。
簡單的列舉
Rust 中的列舉與 C 語言中的列舉類別似,可以用來定義一組具名常數。例如:
enum Ordering {
Less,
Equal,
Greater
}
這個列舉定義了一個名為 Ordering 的型別,它有三個可能的取值:Ordering::Less、Ordering::Equal 和 Ordering::Greater。Rust 允許我們直接使用這些取值,只要事先匯入它們即可:
use std::cmp::Ordering::*;
fn compare(n: i32, m: i32) -> Ordering {
if n < m {
Less
} else if n > m {
Greater
} else {
Equal
}
}
在記憶體中,C 風格的列舉會被儲存為整數。Rust 預設會使用最小的可容納所有取值的整數型別來儲存列舉值。
記憶體表示
預設情況下,Rust 會根據列舉的大小選擇最小的整數型別來儲存它。例如:
use std::mem::size_of;
assert_eq!(size_of::<Ordering>(), 1);
若需要指定列舉的記憶體表示,可以使用 #[repr] 屬性。
列舉與資料
Rust 的列舉不僅可以包含簡單的具名常數,還可以攜帶資料。例如:
#[derive(Copy, Clone, Debug, PartialEq)]
enum RoughTime {
InThePast(TimeUnit, u32),
JustNow,
InTheFuture(TimeUnit, u32)
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum TimeUnit {
Seconds, Minutes, Hours, Days, Months, Years
}
impl TimeUnit {
fn plural(self) -> &'static str {
match self {
TimeUnit::Seconds => "seconds",
TimeUnit::Minutes => "minutes",
TimeUnit::Hours => "hours",
TimeUnit::Days => "days",
TimeUnit::Months => "months",
TimeUnit::Years => "years",
}
}
fn singular(self) -> &'static str {
self.plural().trim_end_matches('s')
}
}
這個 RoughTime 列舉可以表示一個近似的時間點,例如「兩天前」或「三個月後」。它包含了不同型別的資料,並提供了豐富的表達能力。
方法與列舉
如同結構體(Structs),列舉也可以實作方法。例如,我們可以在 TimeUnit 列舉上實作 plural 和 singular 方法,用於取得時間單位的複數和單數形式。
模式匹配
模式匹配(Pattern Matching)是 Rust 中處理列舉和其他複雜資料結構的重要工具。它允許我們根據值的不同形態執行不同的程式碼,從而實作安全且靈活的資料處理。
內容解密:
- 模式匹配的基本概念:模式匹配是一種用於檢查資料結構是否符合某種特定模式,並據此進行不同操作的技術。
- 安全存取資料:透過模式匹配,我們可以安全地存取列舉中的資料,避免因未處理某些情況而導致的執行階段錯誤。
- 簡潔的程式碼:模式匹配使得程式碼更加簡潔和易讀,因為它允許我們在單一行程式碼中完成對資料的多種檢查和操作。
Rust列舉(Enum)詳解:定義、記憶體結構與應用
Rust的列舉(enum)是一種強大的資料型別,能夠表示多種不同的變體(variant),並且每個變體可以包含不同型別的資料。本文將探討Rust中列舉的定義、記憶體結構、以及其在建立豐富資料結構中的應用。
列舉的基本定義與使用
Rust中的列舉允許定義一個型別,該型別可以有多個變體。例如,定義一個RoughTime列舉來表示一個粗略的時間:
enum RoughTime {
InThePast(TimeUnit, i32),
InTheFuture(TimeUnit, i32),
}
enum TimeUnit {
Seconds,
Minutes,
Hours,
Days,
Months,
Years,
}
在這個例子中,RoughTime有兩個變體:InThePast和InTheFuture,每個變體都帶有兩個引數,分別是TimeUnit和一個i32值,表示時間的單位和數量。
建立列舉值
可以使用列舉的變體來建立新的值:
let four_score_and_seven_years_ago = RoughTime::InThePast(TimeUnit::Years, 4*20 + 7);
let three_hours_from_now = RoughTime::InTheFuture(TimeUnit::Hours, 3);
結構變體
除了元組變體外,列舉也可以有結構變體,包含命名的欄位,就像普通的結構一樣:
enum Shape {
Sphere { center: Point3d, radius: f32 },
Cuboid { corner1: Point3d, corner2: Point3d },
}
let unit_sphere = Shape::Sphere { center: ORIGIN, radius: 1.0 };
列舉在記憶體中的表示
在記憶體中,帶有資料的列舉被儲存為一個小的整數標籤,加上足夠的記憶體來儲存最大變體的所有欄位。這個標籤欄位是用於Rust內部使用的,用來識別值的建立者以及它具有哪些欄位。
#### 內容解密:
RoughTime在記憶體中佔用8位元組,其中包含一個標籤和資料。- Rust不對列舉的佈局做出任何承諾,以便為未來的最佳化留有餘地。
使用列舉建立豐富的資料結構
列舉在快速實作樹狀資料結構時非常有用。例如,假設需要在Rust程式中處理任意的JSON資料。在記憶體中,任何JSON檔案都可以表示為以下Rust型別的value:
enum Json {
Null,
Boolean(bool),
Number(f64),
String(String),
Array(Vec<Json>),
Object(Box<HashMap<String, Json>>),
}
#### 內容解密:
Json列舉簡單地描述了JSON標準中指定的各種資料型別。- 使用
Box包裝HashMap可以使所有Json值更為緊湊,因為Box<HashMap>只是一個指向堆積上分配資料的指標。
泛型列舉
列舉可以是泛型的。標準函式庫中的兩個例子是Option<T>和Result<T, E>:
enum Option<T> {
None,
Some(T),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
#### 內容解密:
Option<T>和Result<T, E>是Rust中最常用的資料型別。- 當
T是Box或其他智慧指標型別時,Rust可以消除Option<T>的標籤欄位。
自訂泛型資料結構
可以使用泛型列舉建立自訂的資料結構,例如二元樹:
enum BinaryTree<T> {
Empty,
NonEmpty(Box<TreeNode<T>>),
}
struct TreeNode<T> {
element: T,
left: BinaryTree<T>,
right: BinaryTree<T>,
}
#### 內容解密:
BinaryTree<T>可以儲存任意數量的T型別的值。- Rust透過消除標籤欄位最佳化了
BinaryTree的儲存。
Rust 中的列舉(Enums)與模式匹配(Pattern Matching)
Rust 的列舉(Enums)是一種強大的資料結構,能夠表示多種不同型別的資料。透過模式匹配(Pattern Matching),開發者可以安全且有效地處理列舉中的不同變體。
建立二元樹(Binary Tree)
在 Rust 中,建立複雜的資料結構如二元樹需要謹慎處理所有權(Ownership)和借用(Borrowing)。以下是一個建立二元樹的例子:
let jupiter_tree = NonEmpty(Box::new(TreeNode {
element: "Jupiter",
left: Empty,
right: Empty,
}));
let mars_tree = NonEmpty(Box::new(TreeNode {
element: "Mars",
left: jupiter_tree,
right: mercury_tree,
}));
let tree = NonEmpty(Box::new(TreeNode {
element: "Saturn",
left: mars_tree,
right: uranus_tree,
}));
內容解密:
NonEmpty和Empty是列舉BinaryTree的兩個變體,用於表示非空和空的二元樹節點。TreeNode結構體包含element(節點的值)、left(左子節點)和right(右子節點)。- 使用
Box::new來在堆積疊上分配TreeNode,並將其包裝在NonEmpty中,以遞迴地建立二元樹。
模式匹配(Pattern Matching)
模式匹配是 Rust 中處理列舉和其他複雜資料結構的核心機制。以下是一個使用模式匹配處理 RoughTime 列舉的例子:
enum RoughTime {
InThePast(TimeUnit, u32),
JustNow,
InTheFuture(TimeUnit, u32),
}
fn rough_time_to_english(rt: RoughTime) -> String {
match rt {
RoughTime::InThePast(units, count) => format!("{} {} ago", count, units.plural()),
RoughTime::JustNow => format!("just now"),
RoughTime::InTheFuture(units, count) => format!("{} {} from now", count, units.plural()),
}
}
內容解密:
match表示式用於對RoughTime列舉進行模式匹配。- 每個分支(arm)指定了一種可能的
RoughTime變體及其對應的處理邏輯。 - 模式匹配允許開發者安全地存取列舉內部的資料,並根據不同的變體執行不同的程式碼。
模式的多樣性
Rust 的模式匹配支援多種不同的模式,包括:
- Literal Patterns:匹配特定的字面值。
- Range Patterns:匹配一個範圍內的值。
- Wildcard Patterns:使用
_匹配任意值並忽略它。 - Variable Patterns:將匹配的值繫結到一個新的變數。
- Enum Patterns:匹配列舉的特定變體。
使用模式匹配處理整數
除了列舉之外,模式匹配也可以用於處理整數值:
match meadow.count_rabbits() {
0 => {} // nothing to say
1 => println!("A rabbit is nosing around in the clover."),
n => println!("There are {} rabbits hopping about in the meadow", n),
}
內容解密:
match表示式用於檢查count_rabbits()的傳回值。- 不同的整數值觸發不同的分支,從而執行不同的操作。
模式匹配的探討
在程式設計中,模式匹配是一種強大且富有表現力的工具,用於處理不同情況和資料結構。Rust 語言提供了豐富的模式匹配功能,讓開發者能夠編寫清晰、簡潔且高效的程式碼。
基本模式匹配
模式匹配的基本思想是根據不同的模式對資料進行處理。在 Rust 中,這通常透過 match 表示式來實作。例如,當我們需要根據一個列舉(enum)的值來執行不同的操作時,可以使用 match:
let calendar = match settings.get_string("calendar") {
"gregorian" => Calendar::Gregorian,
"chinese" => Calendar::Chinese,
"ethiopian" => Calendar::Ethiopian,
other => return parse_error("calendar", other),
};
在這個例子中,我們根據 settings.get_string("calendar") 的傳回值進行模式匹配,並傳回相應的 Calendar 型別。如果傳回值不匹配任何已定義的模式,則執行 other 模式下的程式碼。
萬用字元模式
當我們需要處理所有其他未被明確列出的情況時,可以使用萬用字元模式 _:
let caption = match photo.tagged_pet() {
Pet::Tyrannosaur => "RRRAAAAAHHHHHH",
Pet::Samoyed => "*dog thoughts*",
_ => "I'm cute, love me", // 通用的標題,適用於任何寵物
};
在這個例子中,_ 模式匹配任何未被前面模式匹配的值,並傳回一個通用的標題。
內容解密:
match photo.tagged_pet()對photo.tagged_pet()的傳回值進行模式匹配。Pet::Tyrannosaur和Pet::Samoyed是特定的列舉值,分別對應不同的標題。_是萬用字元模式,用於匹配任何其他未被列出的列舉值。
元組和結構模式
Rust 允許對元組和結構進行模式匹配,這使得處理複雜資料結構變得更加容易。
元組模式
fn describe_point(x: i32, y: i32) -> &'static str {
use std::cmp::Ordering::*;
match (x.cmp(&0), y.cmp(&0)) {
(Equal, Equal) => "at the origin",
(_, Equal) => "on the x axis",
(Equal, _) => "on the y axis",
(Greater, Greater) => "in the first quadrant",
(Less, Greater) => "in the second quadrant",
_ => "somewhere else",
}
}
在這個例子中,我們對 (x.cmp(&0), y.cmp(&0)) 的結果進行模式匹配,以確定點 (x, y) 的位置。
內容解密:
(x.cmp(&0), y.cmp(&0))傳回一個元組,包含x和y與0比較的結果。- 模式匹配元組的不同可能值,以傳回相應的位置描述。
結構模式
match balloon.location {
Point { x: 0, y: height } => println!("straight up {} meters", height),
Point { x, y } => println!("at ({}, {})", x, y),
}
在這個例子中,我們對 balloon.location 進行模式匹配,以提取其 x 和 y 欄位的值。
內容解密:
Point { x: 0, y: height }匹配x為0的情況,並將y的值繫結到height變數。Point { x, y }匹配任何Point值,並將其x和y欄位的值繫結到x和y變數。
省略欄位
當結構中有很多欄位,但我們只關心其中幾個時,可以使用 .. 省略其他欄位:
match get_account(id) {
Some(Account { name, language, .. }) => language.show_custom_greeting(name),
// ...
}
內容解密:
Some(Account { name, language, .. })匹配Account結構,並提取name和language欄位的值。..省略了其他欄位,使程式碼更加簡潔。
參考模式
Rust 提供了兩種與參考相關的模式匹配功能:ref 模式和 & 模式。這些功能使我們能夠以更靈活的方式處理參考和所有權。
ref 模式
match account {
Account { ref name, ref language, .. } => {
ui.greet(name, language);
ui.show_settings(&account); // 正確,因為 account 未被移動
}
}
在這個例子中,使用 ref 關鍵字借用 name 和 language 欄位,而不是移動它們。
內容解密:
ref name和ref language分別借用name和language欄位。- 這使得我們可以在後續程式碼中使用
account,因為它沒有被移動。