Skip to main content

Vector 向量 (Vector)

vector 是 Move 內建的集合元素儲存方式。它是一種有序、可增長的集合,類似於其他程式語言中的陣列或串列,並且是其他型別的建構區塊:後續章節介紹的 OptionString 型別都是以 vector 為底層實作。本節將介紹 vector 型別、其操作方式,以及讓操作它更方便的巨集。

Vector 語法 (Vector Syntax)

vector 型別的寫法是使用 vector 關鍵字,後面接上角括號中元素的型別。元素的型別可以是任何有效的 Move 型別,包括其他 vector。

Move 也有 vector 字面值語法,讓你可以使用 vector 關鍵字後面接上包含元素的方括號(空 vector 則不含元素)來建立 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 中的內建型別,不需要從模組匯入。vector 操作是定義在標準函式庫std::vector 模組中,該模組會被隱式匯入,因此可以直接使用而不需要明確的 use 陳述式。

在本節中,我們使用點語法呼叫 vector 函式,例如使用 v.length() 而非 vector::length(&v)。這是所謂的接收者語法(receiver syntax),標準函式庫型別開箱即用即可使用此語法;我們會在結構方法一節中說明其運作原理。

讀取元素 (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 函式的簡寫——它產生的是該元素的參考,而不是元素本身。對於像上面整數這類可複製的型別,這個差異並不明顯;而對於無法複製的型別,要將元素取出 vector 之外,就需要使用下面說明的 pop_backremoveswap_remove。此語法的細節說明於 Move 參考手冊中的索引語法

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

可變的 vector 可以增長也可以縮減。最有效率的操作是作用在 vector 的尾端——也就是 push_backpop_back——而 insertremove 則作用在任意索引位置,並會位移其後所有的元素:

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_emptyvector 沒有元素時回傳 true-
push_back在尾端新增一個元素-
pop_back移除並回傳最後一個元素vector 為空
insert在該索引處插入一個元素,並位移其餘元素索引超出範圍
remove移除並回傳該索引處的元素索引超出範圍
swap_remove將該元素與最後一個元素交換後移除索引超出範圍
swap交換兩個索引處的元素某個索引超出範圍
containsvector 包含該元素時回傳 true-
index_of找到元素時回傳 (true, index)-
append將另一個 vector 的所有元素移動到尾端-
reverse反轉元素的順序-
destroy_empty銷毀一個空的 vectorvector 不是空的

請注意,remove 會位移被移除元素之後的每一個元素,因此 vector 越長,成本就越高。如果元素的順序不重要,swap_remove 可以在常數時間內完成相同的工作。

Vector 巨集 (Vector Macros)

讀取、轉換或彙總 vector 中的每一個元素是非常常見的任務,因此標準函式庫為此提供了一組巨集。巨集的名稱以 ! 結尾,並接受一個匿名函式(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!——它們每一個都能用一行富有表達力的程式碼取代手寫的迴圈。完整清單可在模組文件中找到,而巨集的一般性介紹則在本章稍後的巨集函式一節中說明。

銷毀不具備 Drop 能力型別的 Vector (Destroying a Vector of Non-Droppable Types)

vector 型別會從其元素繼承能力:只有當 T 具備 drop 能力時,vector<T> 才能被丟棄。不具備 drop 能力型別的 vector 無法被忽略,即使它是空的,編譯器也會要求明確呼叫 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();
}

如果你對非空的 vector 呼叫 destroy_empty 函式,該呼叫會在執行期失敗。這正是資源模型運作的方式:如果 vector 的元素代表資產,那麼無論是資產本身還是承載它們的 vector,都不能悄悄地消失——在 vector 本身被銷毀之前,每一個元素都必須被取出並妥善處理。

延伸閱讀 (Further Reading)