Skip to main content

選項 (Option)

有些資料本質上就是選擇性的:使用者可能有、也可能沒有中間名,一次查找可能找到、也可能找不到相符結果。Move 沒有 nullundefined 值——String 型別的變數永遠持有一個字串——所以「值不存在」必須用其他方式表達。

第一直覺可能是保留一個特殊值當標記:用空字串代表缺少中間名,用零代表缺少數字。這種做法可行——直到空字串變成合法輸入,而每個函式都得記住哪些值是「真實的」、哪些只是佔位符。標準函式庫提供了更好的工具:Option 型別,這是 Move 從 Rust 借來的概念。

Option 型別 (The Option Type)

Option<Element> 是對 Element 型別值的包裝,它永遠處於兩種狀態之一,慣例上稱為 SomeNone

  • 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將值放入空的 optionoption 已持有值
extract取出值,使 option 變為空option 為空
swap替換值,回傳舊值option 為空
destroy_some銷毀 option,回傳其值option 為空
destroy_none銷毀空的 optionoption 持有值
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 定義為只有一個欄位的結構:一個 Elementvector,該 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 _就是_一個帶有 SomeNone 變體 (variants) 的 enum——Move 沿用了這套術語。

這種內部表示方式屬於實作細節:上述函式與巨集已涵蓋一般用途,vec 欄位不會被直接存取。

延伸閱讀 (Further Reading)