選項 (Option)
有些資料本質上是可選的:使用者可能有也可能沒有中間名,查詢可能找到也可能找不到相符項目。Move 沒有 null 或 undefined 值——String 型別的變數一定持有一個字串——因此,必須以其他方式表達值不存在的情況。
第一個直覺可能是保留一個特殊值作為標記:以空字串表示缺少的中間名,以零表示缺少的數字。這種方式可行——直到空字串成為有效輸入,而且每個函式都必須記住哪些值是「實際」值、哪些是預留位置。標準函式庫提供了更好的工具:Option 型別,這是 Move 從 Rust 借用的概念。
Option 型別 (The Option Type)
Option<Element> 是 Element 型別值的包裝器,且一定處於兩種狀態之一,慣例上稱為 Some 與 None:
- Some - 此選項包含一個值;
- None - 此選項為空。
選項不會與其包裝的值混淆:Option<String> 並非 String,必須先檢查並取出該值才能使用。值可能不存在的情況會成為型別的一部分,顯示在每個簽章中,而非每個呼叫端都必須記住的慣例。
Option 定義於標準函式庫,且如同 vector,會被隱含匯入——不需要 use 陳述式即可在任何模組中使用。Element 型別參數使其成為泛型:同一份定義可用於 Option<u64>、Option<String> 與任何其他元素型別。
以下是上述問題中的使用者紀錄,並以 Option<String> 表達可選欄位:
module book::user_registry;
use std::string::String;
/// A struct representing a user record.
public struct User has drop {
first_name: String,
middle_name: Option<String>,
last_name: String,
}
/// Create a new `User` struct with the given fields.
public fun register(
first_name: String,
middle_name: Option<String>,
last_name: String,
): User {
User { first_name, middle_name, last_name }
}
middle_name 欄位的型別精確表達了特殊值方式無法表達的內容:該值可能不存在,且沒有任何 String——不論是否為空——被保留作為標記。兩種情況分別使用 option::some(value) 與 option::none() 建立:
// A user with a middle name...
let ada = register(
"Ada",
option::some("King"),
"Lovelace",
);
// ...and a user without one. No reserved values, no guesswork.
let grace = register(
"Grace",
option::none(),
"Hopper",
);
建立與使用 Option (Creating and Using an Option)
建立後,可以檢查選項是否有值、讀取值,以及將其清空:
// `option::some` creates an option holding a value.
let mut opt: Option<String> = option::some("Alice");
// `option::none` creates an empty option. The element type has to
// be specified when it cannot be inferred from use.
let empty: Option<u64> = option::none();
// Checking the state of an option.
assert_eq!(opt.is_some(), true);
assert_eq!(empty.is_none(), true);
// `borrow` reads the value without taking it out of the option.
assert_ref_eq!(opt.borrow(), &"Alice");
// `extract` takes the value out, leaving the option empty.
let inner = opt.extract();
assert_eq!(inner, "Alice");
assert_eq!(opt.is_none(), true);
borrow 函式會產生該值的參考——一種不必將值從選項中取出即可讀取它的方式。本章稍後的參考章節將介紹參考。
下表列出 std::option 模組最常用的函式;完整清單請參閱模組文件:
| 函式 | 說明 | 中止條件 |
|---|---|---|
| is_some | 若選項持有值則回傳 true | - |
| is_none | 若選項為空則回傳 true | - |
| contains | 若選項持有指定值則回傳 true | - |
| borrow | 回傳值的參考 | 選項為空 |
| borrow_mut | 回傳值的可變參考 | 選項為空 |
| fill | 將值放入空選項 | 選項持有值 |
| extract | 取出值,並將選項保留為空 | 選項為空 |
| swap | 取代值,並回傳舊值 | 選項為空 |
| destroy_some | 銷毀選項,並回傳值 | 選項為空 |
| destroy_none | 銷毀空選項 | 選項持有值 |
| destroy_with_default | 銷毀選項,並回傳值或預設值 | - |
如同 vector,Option 會從元素型別繼承其能力:非可丟棄型別的選項不能被忽略,且必須使用上述其中一個 destroy_* 函式明確銷毀。
Option 巨集 (Option Macros)
如同 vector 巨集,選項巨集會以單一運算式取代常見的先檢查再取出序列:
// `destroy_or!` consumes the option, returning a default when empty.
let value = option::some(10u8).destroy_or!(0);
assert_eq!(value, 10);
let missing = option::none<u8>().destroy_or!(0);
assert_eq!(missing, 0);
// `is_some_and!` tests the value against a condition.
let is_big = option::some(10u8).is_some_and!(|n| *n > 5);
assert_eq!(is_big, true);
// `do!` runs the lambda only when there is a value.
option::some(10u8).do!(|n| assert_eq!(n, 10));
其他常用巨集包括 map!、filter!、extract_or! 與 do_ref!——完整清單可見於模組文件,而一般巨集則會在本章稍後的巨集函式章節中介紹。
底層實作 (Under the Hood)
Option 被定義為具有單一欄位的結構:一個 Element 的 vector,它一定為空(None)或恰好持有一個值(Some):
module std::option;
/// 對可能存在或不存在之值的抽象。
public struct Option<Element> has copy, drop, store {
vec: vector<Element>
}
你可能會驚訝於 Option 是包含 vector 的結構,而不是 enum。這是歷史因素:在該語言支援 enum 之前,Option 就已加入 Move。在其型別起源的 Rust 中,Option是具有 Some 與 None變體的 enum——Move 保留了這個術語。
此表示法屬於實作細節:上述函式與巨集涵蓋了一般用法,而 vec 欄位絕不會被直接存取。
延伸閱讀 (Further Reading)
- std::option 模組文件。