選項 (Option)
有些資料本質上就是選擇性的:使用者可能有、也可能沒有中間名,一次查找可能找到、也可能找不到相符結果。Move 沒有 null 或 undefined 值——String 型別的變數永遠持有一個字串——所以「值不存在」必須用其他方式表達。
第一直覺可能是保留一個特殊值當標記:用空字串代表缺少中間名,用零代表缺少數字。這種做法可行——直到空字串變成合法輸入,而每個函式都得記住哪些值是「真實的」、哪些只是佔位符。標準函式庫提供了更好的工具:Option 型別,這是 Move 從 Rust 借來的概念。
Option 型別 (The Option Type)
Option<Element> 是對 Element 型別值的包裝,它永遠處於兩種狀態之一,慣例上稱為 Some 和 None:
- Some - option 包含一個值;
- None - option 是空的。
option 不會與它包裝的值混淆:Option<String> 不是 String,而且必須先檢查值是否存在並取出,才能使用它。「可能不存在」這件事本身成為型別的一部分,在每個函式簽名中都清楚可見,而不是每個呼叫端都必須記住的一項約定。
Option 定義於標準函式庫 (Standard Library),和 vector 一樣是隱式匯入 (implicit imports)——不需要 use 陳述式就能在任何模組中使用。Element 型別參數讓它成為泛型 (generic):同一份定義可以服務 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 可以檢查是否有值、讀取,以及清空:
// `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 函式回傳指向該值的_參考 (reference)_——一種不需將值取出 option 就能讀取的方式。參考將在本章稍後的參考 (References)小節中介紹。
下表列出 std::option 模組中最常用的函式;完整清單請參閱模組文件:
| 函式 | 說明 | 何時中止 (Aborts If) |
|---|---|---|
| is_some | 若 option 持有值則回傳 true | - |
| is_none | 若 option 為空則回傳 true | - |
| contains | 若 option 持有指定的值則回傳 true | - |
| borrow | 回傳指向值的參考 | option 為空 |
| borrow_mut | 回傳指向值的可變參考 | option 為空 |
| fill | 將值放入空的 option | option 已持有值 |
| extract | 取出值,使 option 變為空 | option 為空 |
| swap | 替換值,回傳舊值 | option 為空 |
| destroy_some | 銷毀 option,回傳其值 | option 為空 |
| destroy_none | 銷毀空的 option | option 持有值 |
| destroy_with_default | 銷毀 option,回傳其值或預設值 | - |
和 vector 一樣,Option 的能力繼承自元素型別:非可丟棄 (droppable)型別的 option 不能被忽略,必須用上述 destroy_* 函式之一明確銷毀。
Option 巨集 (Option Macros)
和向量巨集 (vector macros)一樣,option 巨集用單一運算式取代常見的「先檢查再取出」序列:
// `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!——完整清單可在模組文件中找到,巨集的一般性介紹則在本章稍後的巨集函式 (Macro Functions)小節。
底層原理 (Under the Hood)
Option 定義為只有一個欄位的結構:一個 Element 的 vector,該 vector 永遠不是空的(None)就是恰好持有一個值(Some):
module std::option;
/// 表示可能存在也可能不存在的值的抽象。
public struct Option<Element> has copy, drop, store {
vec: vector<Element>
}
你可能會驚訝 Option 是一個包含 vector 的結構,而不是一個 enum。這是歷史因素造成的:Option 在 Move 語言支援 enum 之前就已經加入了。在 Rust(這個型別的發源地)中,Option _就是_一個帶有 Some 和 None 變體 (variants) 的 enum——Move 沿用了這套術語。
這種內部表示方式屬於實作細節:上述函式與巨集已涵蓋一般用途,vec 欄位不會被直接存取。
延伸閱讀 (Further Reading)
- std::option 模組文件。