Skip to main content

儲存函式 (Storage Functions)

定義主要儲存操作的模組是 sui::transfer。它在所有依賴 Sui Framework 的套件中都會被隱式引入,因此,就像其他隱式引入的模組(例如 std::optionstd::vector)一樣,不需要 use 陳述式。

快速參考請見 附錄 C:轉移函式 (Appendix C: Transfer Functions),其中列出了所有儲存函式與物件狀態。

總覽 (Overview)

transfer 模組為物件可以進入的每一種 所有權狀態 都提供了對應的函式:

  1. 轉移 (Transfer) - 將物件傳送給某個地址,使其進入 address owned(地址擁有)狀態;
  2. 凍結 (Freeze) - 將物件置於 immutable(不可變)狀態,使其成為永遠不會改變的 public constant(公開常數);
  3. 共享 (Share) - 將物件置於 shared(共享)狀態,讓所有人都能存取。

transfer 模組是大多數儲存操作的首選工具。有兩個特殊情況將另外說明:下一章的 動態欄位 (Dynamic Fields) — 將資料附加到物件上,以及本章末尾的 接收傳送給其他物件的物件

擁有權與參考:快速回顧 (Ownership and References: a Quick Recap)

儲存函式直接建立在 擁有權與作用域參考 章節的語意之上。所有這些函式都以傳值方式接收物件: 物件被移入函式中,呼叫者失去該物件的擁有權 — 而且,如我們即將看到的,該物件最終會以新的狀態進入儲存。這正是資源模型的運作方式:物件永遠不會被複製進儲存,而是被放置到儲存中,且先前的擁有者確實地放棄了它。另一方面,只需要讀取或更新物件的函式,則以參考的方式接收(&T&mut T),並保持擁有權狀態不變。

Transfer 函式中的內部規則 (Internal Rule in Transfer Functions)

每個儲存操作都有兩種形式:internal(內部)和 public(公開)。內部函式 - transfershare_objectfreeze_object - 強制執行前一節的 internal constraint(內部約束):它們只能在定義該物件型別的模組中被呼叫。公開版本 - 以 public_ 為前綴 - 解除這個限制,但要求該型別除了 key 之外還要有 store

/// 內部:只能在定義 `T` 的模組中呼叫。
public fun transfer<T: key>(obj: T, recipient: address);

/// 公開:可以從任何模組呼叫,但要求 `T` 具有 `store`。
public fun public_transfer<T: key + store>(obj: T, recipient: address);

這兩種形式共同實作了我們在 store ability 一節中預覽過的規則:只有 key 的物件的儲存,完全由其定義模組掌控,而 store 則讓該物件可以被任何模組執行儲存操作 - 以及由擁有者在交易中直接操作。

為了一次看清所有組合,假設模組 book::transfer_a 定義了兩個物件 - 具有 keyObjectK 與具有 key + storeObjectKS - 而模組 book::transfer_b 嘗試轉移 它們:

/// 從 `transfer_a` 匯入 `ObjectK` 與 `ObjectKS` 型別,並嘗試
/// 為它們實作不同的 `transfer` 函式。
module book::transfer_b;

// 這些型別對本模組來說並非內部!
use book::transfer_a::{ObjectK, ObjectKS};

// 失敗!`ObjectK` 不是本模組的內部型別。
public fun transfer_k(k: ObjectK, to: address) {
transfer::transfer(k, to);
}

// 失敗!`ObjectKS` 也不是本模組的內部型別 —
// `store` 不會影響內部函式。
public fun transfer_ks(ks: ObjectKS, to: address) {
transfer::transfer(ks, to);
}

// 失敗!`public_transfer` 要求 `store`,而 `ObjectK` 沒有它。
public fun public_transfer_k(k: ObjectK, to: address) {
transfer::public_transfer(k, to);
}

// 成功!`ObjectKS` 具有 `store`,而且這個函式是公開的。
public fun public_transfer_ks(ks: ObjectKS, to: address) {
transfer::public_transfer(ks, to);
}

同樣的矩陣也適用於 share_object/public_share_object 以及 freeze_object/public_freeze_object。理解這條規則,對於理解 Move 中的 應用程式設計至關重要:讓一個物件可公開轉移(key + store)與保持內部(僅 key)之間的抉擇,會大幅影響應用程式對其資產所能提供的保證。

Transfer 轉移 (Transfer)

transfer::transfer 函式會將物件送到某個地址,讓該地址成為其唯一擁有者:

module sui::transfer;

// 將 `obj` 轉移給 `recipient`。
public fun transfer<T: key>(obj: T, recipient: address);

// `transfer` 函式的公開版本。
public fun public_transfer<T: key + store>(obj: T, recipient: address);

在以下範例中,模組建立了一個代表應用程式管理員權限的物件,並將它送給模組的發布者:

/// A struct with `key` is an object. The first field is `id: UID`!
public struct AdminCap has key { id: UID }

/// `init` is a special function called once, when the module is
/// published. It is the best place to create singleton objects,
/// such as an admin capability.
fun init(ctx: &mut TxContext) {
// Create the `AdminCap` object in this scope.
let admin_cap = AdminCap { id: object::new(ctx) };

// Transfer the object to the transaction sender.
transfer::transfer(admin_cap, ctx.sender());
}

/// Transfers the `AdminCap` object to the `recipient`. Thus, the
/// recipient becomes the owner of the object, and only they can
/// access it.
public fun transfer_admin_cap(cap: AdminCap, recipient: address) {
transfer::transfer(cap, recipient);
}

當模組被發布時,init 函式會被呼叫,其中建立的 AdminCap 物件會被_轉移_給交易的發送者 —— ctx.sender() 會回傳目前交易的發送者地址。(init 函式在模組初始化器章節中有詳細說明。)

從此之後,假設發送者是 0xa11ce,該物件就處於_地址擁有_狀態:只有 0xa11ce 能在交易中使用它——無論是透過參考或值傳遞,包括用上方的 transfer_admin_cap 函式繼續轉移它。

地址擁有的物件受_真正擁有權_約束——只有擁有者地址能存取它們。這是 Sui 儲存模型中的基本概念,已在擁有權章節中介紹過。

公開轉移 (Public Transfer)

讓我們擴充範例,加入一個使用 AdminCap 來授權鑄造新物件並將其轉移給任意地址的函式:

/// Some `Gift` object that the admin can `mint_and_transfer`.
public struct Gift has key, store { id: UID }

/// Creates a new `Gift` object and transfers it to the `recipient`.
public fun mint_and_transfer(
_: &AdminCap,
recipient: address,
ctx: &mut TxContext,
) {
let gift = Gift { id: object::new(ctx) };
transfer::public_transfer(gift, recipient);
}

mint_and_transfer 函式「理論上」任何人都能呼叫——它是公開的——但它要求第一個參數必須是 AdminCap 參考,而 AdminCap 物件是由 0xa11ce 獨自擁有的。所以實際上只有 0xa11ce 能鑄造。這種簡單明確的方式來限制對函式的存取,就是_能力模式_,是 Sui 應用程式設計的基石之一。

注意這個範例中兩個物件的差異。AdminCap 只有 key:模組對它保有完全的控制權,如果模組沒有公開 transfer_admin_cap 函式,管理員權限就會是_靈魂綁定_的——無法轉讓出去。Gift 則具有 key + store:它是用 public_transfer 送出的,任何擁有 Gift 的人都能在自己的交易中自由地繼續轉移它,不需要這個模組的任何協助。

快速回顧 (Quick Recap)

  • transfer 會將物件送到某個地址,使其成為_地址擁有_;
  • 只有擁有者能使用地址擁有的物件——無論是透過參考或值傳遞;
  • 要求一個只有 key 的物件作為參數,能將函式的存取權限制在物件擁有者身上——這就是_能力_模式;
  • public_transfer 是公開版本:任何地方都能呼叫,但要求 key + store

凍結 (Freeze)

transfer::freeze_object 函式會把物件轉為 不可變 狀態。物件一旦被 凍結,就永遠無法改變,任何人都可以透過不可變參考存取它:

module sui::transfer;

// 讓物件變成不可變,並允許任何人讀取它。
public fun freeze_object<T: key>(obj: T);

// `freeze_object` 函式的公開版本。
public fun public_freeze_object<T: key + store>(obj: T);

讓我們用一個由管理員建立並凍結的 Config 物件來延伸這個範例:

/// Some `Config` object that the admin can `create_and_freeze`.
public struct Config has key {
id: UID,
message: String,
}

/// Creates a new `Config` object and freezes it.
public fun create_and_freeze(
_: &AdminCap,
message: String,
ctx: &mut TxContext,
) {
let config = Config {
id: object::new(ctx),
message,
};

// Freeze the object so it becomes immutable.
transfer::freeze_object(config);
}

/// Returns the message from the `Config` object.
/// Can access the object by immutable reference!
public fun message(c: &Config): String { c.message }

一旦呼叫 create_and_freezeConfig 就會透過其 ID 公開可用,任何人都能呼叫 message 函式 —— 對於一個凍結的物件,不可變參考是人人都能自由取用的。

函式的定義與物件的狀態無關,因此定義一個以可變參考或以值取用凍結型別的函式, 在語法上完全合法 —— 只是這些函式無法用凍結物件來呼叫:

/// The function can be defined, but it won't be callable on a frozen
/// object - only immutable references to it are available.
public fun message_mut(c: &mut Config): &mut String { &mut c.message }

同樣的規則也適用於下方分享 (Share) 段落中定義的 delete_config:它以值取用 Config,而一個凍結的 Config 永遠無法傳入其中。凍結是_永久性_的: 一個凍結的物件無法被修改、轉移、刪除 —— 也無法解凍。

單一擁有者 → 凍結 (Owned → Frozen)

由於 freeze_object 的簽章接受任何以值傳入的物件,它既可以接收在同一作用域中 建立的物件,也可以接收發送者_擁有_的物件。從單一擁有者轉換為不可變狀態是可行的! 舉例來說,Gift 的擁有者可以決定將它永久保存:

/// Freezes the `Gift` object so it becomes immutable.
/// `Gift` has `key` + `store`, so `public_freeze_object` can be used!
public fun freeze_gift(gift: Gift) {
transfer::public_freeze_object(gift);
}

出於顯而易見的安全考量,反方向的情況也同樣值得留意:AdminCap 絕不能被凍結 —— 一旦被凍結的權限物件,就會變成任何人都可讀取,而每個以 &AdminCap 把關的 函式也會變成任何人都能呼叫。這再次凸顯了僅使用 key 這種模式的價值:AdminCap 沒有 store,外部程式碼無法將它凍結,而模組本身也根本不對外提供凍結函式。

快速回顧 (Quick Recap)

  • freeze_object 會把物件轉為_不可變_狀態 —— 且是永久性的;
  • 凍結的物件可供任何人透過不可變參考讀取,且永遠無法被修改、轉移或刪除;
  • 已擁有的物件可以被凍結 —— 若物件具有 store,甚至可以由擁有者在交易中凍結;
  • public_freeze_object 是公開版本:可在任何地方呼叫,但要求 key + store

分享 (Share)

transfer::share_object 函式會將物件放入 共享 狀態,讓任何人都能透過可變參考(因此也包含不可變參考)存取它:

module sui::transfer;

/// 將物件放入共享狀態 — 讓所有人都能存取。
public fun share_object<T: key>(obj: T);

/// `share_object` 函式的公開版本。
public fun public_share_object<T: key + store>(obj: T);
/// Creates a new `Config` object and shares it.
public fun create_and_share(message: String, ctx: &mut TxContext) {
let config = Config {
id: object::new(ctx),
message,
};

// Share the object so it becomes shared.
transfer::share_object(config);
}

與同時接受新物件及已擁有物件的 freeze_object 不同,share_object 有一項執行期限制:只有在同一筆交易中建立的物件才能被共享。若嘗試共享一個已存在於擁有狀態的物件,交易會以 ESharedNonNewObject 中止。並不存在 Owned → Shared 的轉換:是否要讓物件成為共享狀態,必須在物件建立時就決定。而且和凍結一樣,共享是單向的——一旦共享,物件在其餘生都會維持共享狀態,唯一的例外我們接下來會看到。

特殊案例:共享物件的刪除 (Special Case: Shared Object Deletion)

雖然共享物件通常無法以值的方式取用,但有一種特殊情況例外——如果取用它的函式會將其刪除。這是 Sui 儲存模型中的一個特殊案例,用來允許清理共享狀態。讓我們新增一個刪除共享 Config 的函式:

/// Deletes the `Config` object, takes it by value.
/// Can be called on a shared object!
public fun delete_config(c: Config) {
let Config { id, message: _ } = c;
id.delete()
}

delete_config 函式以值的方式取用 Config,並將其完全銷毀——解構該結構並刪除 UID——Sui 驗證器允許這樣的呼叫。然而,若該函式回傳了 Config,或嘗試 transferfreeze 它,交易就會被拒絕:

// 行不通!
public fun transfer_shared(c: Config, to: address) {
transfer::transfer(c, to);
}

規則:以值取用的共享物件,必須在同一筆交易中被刪除。

快速回顧 (Quick Recap)

  • share_object 會將物件放入 共享 狀態,讓任何人都能透過可變參考存取;
  • 只有在同一筆交易中建立的物件才能被共享——不存在 Owned → Shared 的轉換;
  • 共享是永久性的,唯一的例外:共享物件可以被以值取用,以便進行 刪除
  • public_share_object 是公開形式:可在任何地方呼叫,需要 key + store

Party Transfer 派對轉移 (Party Transfer)

transfer 模組還提供了 party_transferpublic_party_transfer,可將物件放入 party 狀態 —— 具備共識排序的單一擁有者存取模式。Party 物件是進階的較新功能,我們將其排除在本範例之外;函式簽名列於 附錄 C,詳細內容則涵蓋於 sui::party 模組文件中。

總結 (Summary)

函式結果狀態是否可逆公開版本
transfer地址擁有是 - 可以再轉走public_transfer
freeze_object不可變public_freeze_object
share_object共享只能透過刪除public_share_object
party_transferParty取決於權限public_party_transfer
  • 每個儲存函式都是以傳值方式接收物件——把物件放進儲存中會消耗它;
  • 內部版本要求該型別必須定義在呼叫的模組中;public_* 版本則要求該型別具備 store 能力。

下一步 (Next Steps)

現在你已經了解 transfer 模組的主要功能,可以開始建構涉及儲存操作的應用程式了。在下一節中,我們會介紹 UID 與 ID 型別——每個物件的身分——之後則是 接收為物件 (Receiving as Object),也就是物件擁有其他物件背後的機制。

進階閱讀 (Further Reading)