Skip to main content

向量 (Vector)

vector 是 Move 中用來儲存元素集合的內建方式。它是有序、可擴增的 集合,類似其他程式語言中的陣列或串列,也是其他型別的建構基礎:後續章節中介紹的 Option 與 String 型別皆以向量為基礎。本節將介紹 vector 型別、其操作,以及讓使用更便利的巨集。

向量語法 (Vector Syntax)​

vector 型別使用 vector 關鍵字,後接以角括號包圍的元素型別來表示。元素型別可以是 任何有效的 Move 型別,包括其他向量。

Move 也提供向量字面值語法,讓你能使用 vector 關鍵字,後接包含元素的方括號來建立 向量(若要建立空向量,方括號中不放任何元素)。

// An empty vector of bool elements.
let empty: vector<bool> = vector[];

// A vector of u8 elements.
let v: vector<u8> = vector[10, 20, 30];

// A vector of vector<u8> elements.
let vv: vector<vector<u8>> = vector[
vector[10, 20],
vector[30, 40]
];

vector 型別是 Move 的內建型別,不需要從模組匯入。向量操作定義於 標準函式庫的 std::vector 模組中;該模組會隱式匯入,因此可直接 使用,無須明確的 use 陳述式。

本節使用點語法呼叫向量函式,例如使用 v.length(),而非 vector::length(&v)。這是 接收者語法,標準函式庫型別預設即可使用;我們會在 結構方法章節說明其運作方式。

讀取元素 (Reading Elements)​

對集合最基本的查詢是其大小與元素。length 函式會回傳元素數量,is_empty 用來判斷 是否沒有元素,而索引語法 v[i] 可存取單一元素。索引從零開始,存取超出範圍的索引會 中止執行:

let v: vector<u8> = vector[10, 20, 30];

// `length` returns the number of elements.
assert_eq!(v.length(), 3);
assert_eq!(v.is_empty(), false);

// The index syntax borrows an element; for copyable
// types the borrowed value can be read directly.
assert_eq!(v[0], 10);

// Accessing an index outside of bounds aborts:
// v[3]; // ABORTS!

v[i] 語法是呼叫 borrow 函式的簡寫——它產生元素的 參考,而非元素本身。對於可複製的型別,例如上方的整數,差異並不明顯; 對於無法複製的型別,若要將元素從向量中 取出,必須使用下方說明的 pop_back、remove 或 swap_remove。此語法的詳細內容請參閱 Move 參考文件中的 索引語法。

新增與移除元素 (Adding and Removing Elements)​

可變向量可以擴增與縮減。最有效率的操作作用於向量的 末端——push_back 與 pop_back——而 insert 與 remove 則作用於任意索引,並會位移其後的所有元素:

let mut v = vector[10u8, 20, 30];

// `push_back` adds an element to the end of the vector.
v.push_back(40); // [10, 20, 30, 40]

// `pop_back` removes the last element and returns it.
let last = v.pop_back(); // [10, 20, 30]
assert_eq!(last, 40);

// `insert` places an element at the given index, shifting
// the elements after it to the right.
v.insert(15, 1); // [10, 15, 20, 30]

// `remove` takes an element out at the given index, shifting
// the elements after it to the left.
let removed = v.remove(2); // [10, 15, 30]
assert_eq!(removed, 20);

// The index syntax can also modify an element in place; the `&mut`
// and `*` in this expression are explained in the References section.
*(&mut v[0]) = 5; // [5, 15, 30]
assert_eq!(v[0], 5);

下表列出 std::vector 模組最常使用的函式;完整清單請參閱 模組文件:

函式說明中止條件
length回傳元素數量-
is_empty若向量沒有元素則回傳 true-
push_back將元素新增至末端-
pop_back移除並回傳最後一個元素向量為空
insert在索引處插入元素,並位移其餘元素索引超出範圍
remove移除並回傳索引處的元素索引超出範圍
swap_remove將元素與最後一個元素交換後移除索引超出範圍
swap交換兩個索引處的元素任一索引超出範圍
contains若向量包含該元素則回傳 true-
index_of若找到元素則回傳 (true, index)-
append將另一個向量的所有元素移至末端-
reverse反轉元素順序-
destroy_empty銷毀空向量向量不為空

請注意,remove 會位移被移除元素之後的每個元素,因此向量越長,其成本越高。若元素順序 不重要,swap_remove 可在固定時間內完成相同工作。

向量巨集 (Vector Macros)​

讀取、轉換或彙總向量中的每個元素是很常見的工作,因此標準函式庫為此提供了一組 巨集。 巨集名稱以 ! 結尾,並接受一個 lambda(寫成 |argument| expression 的內嵌函式), 巨集會將其套用至元素。在底層,巨集會在編譯時展開為一般迴圈,因此在執行階段不會產生 額外成本:

let v = vector[1u64, 2, 3, 4];

// `count!` returns the number of elements matching the condition.
let even_count = v.count!(|n| *n % 2 == 0);
assert_eq!(even_count, 2);

// `map!` transforms each element, returning a new vector.
let doubled = v.map!(|n| n * 2);
assert_eq!(doubled, vector[2, 4, 6, 8]);

// `fold!` collapses the vector into a single value,
// in this case - the sum of all elements.
let sum = v.fold!(0, |acc, n| acc + n);
assert_eq!(sum, 10);

// `do!` calls the function on each element of the vector.
let mut total = 0u64;
v.do!(|n| total = total + n);
assert_eq!(total, 10);

其他常用巨集包括 filter!、any!、all!、find_index! 與 tabulate!——它們各自能以 一行具表達力的程式碼取代手寫迴圈。完整清單可見於模組文件;本章稍後的 巨集函式章節也會介紹巨集的一般用法。

銷毀包含不可丟棄型別的向量 (Destroying a Vector of Non-Droppable Types)​

vector 型別會從其元素繼承能力:僅當 T 可以 丟棄時,vector<T> 才能被丟棄。即使向量為空,不具 drop 能力的型別 所組成的向量也不能被忽略;編譯器要求明確呼叫 destroy_empty 函式:

/// A struct without `drop` ability.
public struct NoDrop {}

#[test]
fun test_destroy_empty() {
// Initialize a vector of `NoDrop` elements.
let v = vector<NoDrop>[];

// While we know that `v` is empty, we still need to call
// the explicit `destroy_empty` function to discard the vector.
v.destroy_empty();
}

若你對非空向量呼叫 destroy_empty 函式,它會在執行階段失敗。這正是資源模型的運作方式: 若向量元素代表資產,資產本身與持有它們的向量都不能悄然消失——必須先取出並處理每個元素, 才能銷毀向量本身。

延伸閱讀 (Further Reading)​