爲什麼 Rust 枚舉這麼酷?

簡介

面嚮對象語言 (OOP) 的枚舉相當無聊。它們只是給某些常量命名的一種方式。Rust 枚舉更強大。它們比 OOP 更優雅地解決了一些設計問題。繼續讀下去,看看 Rust 枚舉能做哪些 OOP 枚舉不能做的事情。

OOP 枚舉

讓我們看一下 C# 中的枚舉:

enum Colour {
    Red,
    Green,
    Blue
}

你能用它做什麼?你可以將它與其他相同類型的值進行比較:

var colour1 = Colour.Red;
var colour2 = Colour.Green;
//Compare colour1 and colour2 and do something if equal
if (colour1 == colour2) {
    //do something
}

或者你可以把它們轉換成整數:

var colourInt = (int)colour1;

現在,我並沒有完全放棄 OOP 語言中的枚舉。它們確實提高了類型安全性,並限制了變量可能具有的值。更不用說對普通整數的可讀性了。這樣可以使代碼更乾淨,並減少出現 bug 的機會。但是 OOP 枚舉還沒有充分發揮其潛力。

Rust 枚舉

當編程語言允許枚舉隨身攜帶數據時,枚舉的真正威力就開始顯現了。

Option

Rust 的 Option 是一個值的容器。容器可以是空的,也可以保存一些值。它是這樣定義的:

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

在 Option 中,None 變量表示空狀態,Some 變量可以攜帶類型 T 的數據。爲什麼這很有用?想想如何在 c# 中實現類似的東西,需要是一個帶有標誌的類,指示值是否存在。事實上,c# 中的 Nullable 是類似於這樣定義的:

class Nullable<T> {
    public bool hasValue;
    public T value;
}

現在考慮一下這個類的可用性。如何從 Nullable 實例中獲取值:

var mightBeNull = new Nullable<string>();
if (mightBeNull.hasValue) {
    //do something with mightBeNull.value
}

上面代碼中的 if 非常關鍵。如果你省略了它,那麼 mightBeNull.value.Length 表達式將拋出一個 NullReferenceException:

var mightBeNull = new Nullable<string>();
//no compiler error but still a NullReferenceException
var length = mightBeNull.value.Length;

與之形成鮮明對比的是,你不能直接訪問 Rust 中的值:

let might_be_null: Option<String> = Option::None;
//error[E0609]: no field `value` on type `Option<String>`
let some_other_var = might_be_null.value;

相反,Rust 編譯器會強迫你在得到 Option 在裏面的值之前檢查 mightBeNull 變量是否爲 Some 變量:

let might_be_null: Option<String> = //get an Option<String> from somewhere
if let Some(value) = might_be_null {
    //do something with value
}

很酷,不是嗎?在使用 c# 時搬起石頭砸自己的腳,但是 Rust 阻止了你犯下這些愚蠢的錯誤。讓我們看另一個枚舉。

Result

Result 是 Rust 中錯誤處理的核心。任何函數即可以返回成功,也可以返回錯誤。下面是 Result 的定義:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

這裏 Result 攜帶兩類數據,如果函數成功執行,Ok(T) 將攜帶值返回。如果失敗,Err(E) 將攜帶錯誤返回。

如果你用過 C 語言,那麼一定看到過這種錯誤處理模式,返回值可以同時表示成功和錯誤。例如,atof 函數在 C 語言中是這樣聲明的:

double atof(const char* str);

這個函數將嘗試從 str 中解析一個 double。如果函數成功,它將返回解析後的值。但是如果它失敗,則會返回 0。那如果輸入的字符串是 "0",這也將返回 0。它意味着如果 atof 返回 0,就無法判斷這是因爲輸入字符串是 “0”,還是解析錯誤返回的 0。

在 Rust 中,同樣的函數會有一個更清晰的返回類型:

fn atof(str: &str) -> Result<f64, u8> {
    //...
}

這個 atof 成功解析一個數字時,將返回 Ok(f64)。如果不能解析,將返回 Err(u8)。不可能使用某個有效值作爲錯誤代碼,因爲 Ok 和 Err 變量攜帶單獨的值。

與 Option 一樣,你不能直接獲得 Result 的值, 唯一安全的獲取值的方法是匹配 atof 的返回值:

match atof("123.56") {
    Ok(val) => { println!("Parsed value is: {}", val)}
    Err(e) => { println!("Error is {}", e)}
}

最後,Result 還支持一個安全特性。在 C 語言中,很容易忘記檢查錯誤代碼。在 Rust 中,如果你沒有使用 Result,編譯器會警告你。

何時使用枚舉

內置的 Option 和 Result 類型很棒,但是如何設計自己的枚舉呢?通常,如果變量表示幾種狀態中的任何一種時,枚舉可能是一個很好的選擇。考慮下面來自 serde-yaml crate 的例子:

pub enum Value {
    /// Represents a YAML null value.
    Null,
    /// Represents a YAML boolean.
    Bool(bool),
    /// Represents a YAML numerical value, whether integer or floating point.
    Number(Number),
    /// Represents a YAML string.
    String(String),
    /// Represents a YAML sequence in which the elements are
    /// `serde_yaml::Value`.
    Sequence(Sequence),
    /// Represents a YAML mapping in which the keys and values are both
    /// `serde_yaml::Value`.
    Mapping(Mapping),
}

這裏的 Value 枚舉表示一個 yaml 文件中的值,值可以是 null,也可以是 bool 或數字等等。因此,這是 enum 最佳的選擇。

本文翻譯自:

https://hashrust.com/blog/why-rust-enums-are-so-cool/

coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/X_qJ84Z9uBaL4codHYrweg