返回文章列表

Rust列舉與模式匹配深入解析

Rust 的列舉(Enums)允許定義帶資料的型別,提升程式碼表達力。模式匹配(Pattern Matching)則提供安全有效的方式處理列舉的不同變體,實作更簡潔、安全的程式碼。本文探討 Rust

程式語言 Rust

Rust 的列舉型別不僅能定義具名常數,還能包含不同型別的資料,為程式設計帶來更大的彈性。搭配模式匹配,更能安全地處理列舉的各種變體,避免執行階段錯誤,同時簡化程式碼邏輯。理解列舉的記憶體表示有助於撰寫更高效的程式碼,例如透過 #[repr] 屬性指定記憶體佈局。結合泛型,更能建立通用的資料結構,例如 OptionResult,或自定義的二元樹,展現 Rust 在資料結構設計上的彈性與效能優勢。

列舉與模式匹配

電腦科學中有許多概念看似複雜,但若以某種特定的角度切入,便能豁然開朗。列舉(Enums)便是其中之一。Rust 語言中的列舉不僅可以用來定義一組具名常數,更可以包含不同型別的資料,使得程式設計更加靈活且富有表達力。

簡單的列舉

Rust 中的列舉與 C 語言中的列舉類別似,可以用來定義一組具名常數。例如:

enum Ordering {
    Less,
    Equal,
    Greater
}

這個列舉定義了一個名為 Ordering 的型別,它有三個可能的取值:Ordering::LessOrdering::EqualOrdering::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 列舉上實作 pluralsingular 方法,用於取得時間單位的複數和單數形式。

模式匹配

模式匹配(Pattern Matching)是 Rust 中處理列舉和其他複雜資料結構的重要工具。它允許我們根據值的不同形態執行不同的程式碼,從而實作安全且靈活的資料處理。

內容解密:

  1. 模式匹配的基本概念:模式匹配是一種用於檢查資料結構是否符合某種特定模式,並據此進行不同操作的技術。
  2. 安全存取資料:透過模式匹配,我們可以安全地存取列舉中的資料,避免因未處理某些情況而導致的執行階段錯誤。
  3. 簡潔的程式碼:模式匹配使得程式碼更加簡潔和易讀,因為它允許我們在單一行程式碼中完成對資料的多種檢查和操作。

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有兩個變體:InThePastInTheFuture,每個變體都帶有兩個引數,分別是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中最常用的資料型別。
  • TBox或其他智慧指標型別時,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,
}));

內容解密:

  • NonEmptyEmpty 是列舉 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::TyrannosaurPet::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)) 傳回一個元組,包含 xy0 比較的結果。
  • 模式匹配元組的不同可能值,以傳回相應的位置描述。

結構模式

match balloon.location {
    Point { x: 0, y: height } => println!("straight up {} meters", height),
    Point { x, y } => println!("at ({}, {})", x, y),
}

在這個例子中,我們對 balloon.location 進行模式匹配,以提取其 xy 欄位的值。

內容解密:

  • Point { x: 0, y: height } 匹配 x0 的情況,並將 y 的值繫結到 height 變數。
  • Point { x, y } 匹配任何 Point 值,並將其 xy 欄位的值繫結到 xy 變數。

省略欄位

當結構中有很多欄位,但我們只關心其中幾個時,可以使用 .. 省略其他欄位:

match get_account(id) {
    Some(Account { name, language, .. }) => language.show_custom_greeting(name),
    // ...
}

內容解密:

  • Some(Account { name, language, .. }) 匹配 Account 結構,並提取 namelanguage 欄位的值。
  • .. 省略了其他欄位,使程式碼更加簡潔。

參考模式

Rust 提供了兩種與參考相關的模式匹配功能:ref 模式和 & 模式。這些功能使我們能夠以更靈活的方式處理參考和所有權。

ref 模式

match account {
    Account { ref name, ref language, .. } => {
        ui.greet(name, language);
        ui.show_settings(&account); // 正確,因為 account 未被移動
    }
}

在這個例子中,使用 ref 關鍵字借用 namelanguage 欄位,而不是移動它們。

內容解密:

  • ref nameref language 分別借用 namelanguage 欄位。
  • 這使得我們可以在後續程式碼中使用 account,因為它沒有被移動。