Skip to main content

集合 (Collections)

在程式中儲存一組值是最常見的需求之一。Move 基礎章節介紹的 vector 型別是實現此需求的基本建構區塊,而 Sui Framework 在其上擴充了兩種集合型別:用於保持元素唯一性的 VecSet,以及將鍵與值建立關聯的 VecMap。本節將介紹這三種型別最常見的用途——作為物件的欄位——並說明各自的操作與限制。

向量 (Vector)​

雖然 vector 章節將 vector 型別介紹為獨立值,但在實際應用程式中, 它通常存在於物件內。一間擁有書籍清單的商店,會將向量作為欄位:

module book::collections_vector;

use std::string::String;

/// The Book that can be sold by a `BookStore`
public struct Book has key, store {
id: UID,
name: String
}

/// The BookStore that sells `Book`s
public struct BookStore has key, store {
id: UID,
books: vector<Book>
}

向量章節的所有內容在此都同樣適用;下列集合型別遵循相同模式——可放入欄位、傳遞使用的 一般 struct 值;與本章後續介紹的動態欄位不同,這些集合型別可由持有它們 之物件的型別完整描述。

VecSet 唯一元素集合 (VecSet)​

VecSet 是儲存唯一項目的集合。插入已存在的值會中止,因此集合很適合不得包含重複項目的 集合,例如 ID 或地址清單。

module book::collections_vec_set;

use sui::vec_set::{Self, VecSet};

public struct App has drop {
/// `VecSet` used in the struct definition
subscribers: VecSet<address>
}

#[test_only]
use std::unit_test::assert_eq;

#[test]
fun vec_set_playground() {
let mut set = vec_set::empty(); // create an empty set

set.insert(1u8); // add items to the set
set.insert(2);
set.insert(3);

assert_eq!(set.contains(&1), true); // check if an item is in the set
assert_eq!(set.length(), 3); // get the number of items in the set
assert_eq!(set.is_empty(), false); // check if the set is empty

set.remove(&2); // remove an item from the set
assert_eq!(set.contains(&2), false);

// the contents can be taken out as a plain vector, e.g. for iteration
let items = set.into_keys();
assert_eq!(items, vector[1, 3]);
}

contains 函式可判斷成員資格;你可以透過 keys 以參考方式讀取內容,或使用 into_keys 將內容取出為一般 vector——例如使用 vector 巨集 走訪其中元素。

VecSet 的元素型別必須具有 copy 與 drop 能力。基礎型別與簡單資料 struct 都符合此條件, 但這表示無法在集合中儲存資產。

VecMap 鍵值對集合 (VecMap)​

VecMap 是鍵值對集合,其中每個鍵皆為唯一,並對應單一值。讀取值是 map 的日常操作, 而且有兩種方式:索引語法 map[&key] 會借用一個值,若鍵不存在則中止;try_get 則回傳 Option,且永遠不會中止。

module book::collections_vec_map;

use std::string::String;
use sui::vec_map::{Self, VecMap};

public struct Metadata has drop {
name: String,
/// `VecMap` used in the struct definition
attributes: VecMap<String, String>
}

#[test_only]
use std::unit_test::{assert_eq, assert_ref_eq};

#[test]
fun vec_map_playground() {
let mut map: VecMap<u64, String> = vec_map::empty(); // create an empty map

map.insert(2, "two"); // add a key-value pair to the map
map.insert(3, "three");

assert_eq!(map.contains(&2), true); // check if a key is in the map
assert_eq!(map.length(), 2); // get the number of entries

// index syntax borrows a value by key, aborts if the key is missing
assert_ref_eq!(&map[&2], &"two");

// `try_get` copies the value, returns `none` if the key is missing
assert_eq!(map.try_get(&2), option::some("two"));
assert_eq!(map.try_get(&4), option::none());

// an existing value can be replaced through a mutable reference
*(&mut map[&3]) = "III";

// `remove` returns the key-value pair
let (key, value) = map.remove(&2);
assert_eq!(key, 2);
assert_eq!(value, "two");
}

與 VecSet 相同,VecMap 嘗試 insert 已存在的鍵時會中止——它不會悄悄覆寫舊值。 若要取代值,必須如上述範例所示透過可變參考,或先移除舊項目。VecMap 的鍵必須具有 copy 能力,而值可以是任何型別。

限制 (Limitations)​

以向量為基礎的集合具有嚴格型別:VecSet<address> 僅可保存地址,不能保存其他內容。 這通常正是你所需,但也使其不適合異質資料。它們也是儲存在物件內的一般值,因此會計入 依限制建置指南所述的 256KB 物件大小限制。

實務上,另一項限制更早成為問題:每項操作——insert、contains、get——都會逐一掃描 底層向量元素,因此每次存取的成本會隨集合大小增加。以向量為基礎的集合適合元素數量小且 有界的情況——數十或數百個項目。對於大型或無界集合,Sui Framework 提供 Table、Bag 及其他以物件為基礎的型別,將於本章後續的動態集合章節介紹。

最後,以向量為基礎的集合不支援可能符合預期的相等性比較。VecSet 與 VecMap 會依插入 順序保存內容,且 == 運算子會逐一比較底層向量的元素。因此,包含相同元素但以不同順序 接收元素的兩個集合不會相等。

此行為會由 linter 偵測並發出警告:比較型別為 'sui::vec_set::VecSet' 的集合可能產生非預期結果

let mut set1 = vec_set::empty();
set1.insert(1u8);
set1.insert(2);

let mut set2 = vec_set::empty();
set2.insert(2);
set2.insert(1);

assert_eq!(set1, set2); // aborts!

在上述範例中,兩個集合都包含相同元素——1 與 2——但它們以不同順序插入。由於比較會 考量順序,set1 == set2 的結果為 false,而斷言會中止。除非你能保證元素以相同順序插入, 否則請勿依賴 == 比較以向量為基礎的集合。

總結 (Summary)​

  • 向量是可儲存項目清單的原生型別;在物件內,它會作為一般欄位出現。
  • VecSet 建立於向量之上並儲存唯一項目;插入重複項目會中止。
  • VecMap 儲存具有唯一鍵的鍵值對;插入既有鍵會中止,而值可透過索引語法或 try_get 讀取。
  • 以向量為基礎的集合具有嚴格型別、每次操作皆會線性掃描內容,最適合小型且有界的集合與 清單;較大的集合則適合使用動態集合。

後續步驟 (Next Steps)​

下一節將介紹包裝器型別模式——一種常與集合型別搭配使用,用於擴充 或限制其行為的設計模式。

延伸閱讀 (Further Reading)​