能力 (Abilities)
能力 (Abilities) 是 Move 中的一項類型功能,用於控制對給定類型的數值允許執行哪些操作。此系統可以對數值的「線性 (linear)」類型行為進行細粒度控制,並決定數值是否以及如何在存儲中使用(如 Move 的具體部署所定義的,例如區塊鏈的存儲概念)。這是透過對某些位元組碼指令進行存取控制來實現的,因此數值若要與該位元組碼指令一起使用,就必須具備所需的能力(如果需要的話 —— 並非所有指令都受能力的限制)。
對於 Sui 而言,key 被用來表示一項 物件 (Object)。物件是存儲的基本單位,每個物件都有一個唯一的 32 位元組 ID。store 則用於指示哪些資料可以存儲在物件內部,同時也用於指示哪些類型可以被傳輸到其定義模組之外。
四種能力
這四種能力分別是:
- copy
- 允許具有此能力的類型的數值被複製。
- drop
- 允許具有此能力的類型的數值被彈出 (popped) 或丟棄 (dropped)。
- store
- 允許具有此能力的類型的數值存在於存儲中的某個數值內。
- 對於 Sui 而言,store 控制哪些資料可以存儲在 物件 (Object) 內部。store 還控制哪些類型可以被傳輸到其定義模組之外。
- key
- 允許該類型作為存儲的「鍵 (key)」。表面上,這意味著該數值可以作為存儲中的頂層數值;換句話說,它不需要包含在另一個數值中即可存在於存儲中。
- 對於 Sui 而言,key 被用來表示一項 物件 (Object)。
copy
copy 能力允許具有該能力的類型的數值被複製。它限制了使用 copy 運算子從本地變數複製數值的能力,以及透過解引用 *e 透過引用複製數值的能力。
如果一個數值具有 copy,則該數值內包含的所有數值也都具有 copy。
drop
drop 能力允許具有該能力的類型的數值被丟棄。所謂丟棄,是指該數值未被傳輸,並且在 Move 程式執行時被有效地銷毀。因此,這種能力限制了在多處位置忽略數值的能力,包括:
- 在本地變數或參數中不使用該數值
- 在透過 ; 連接的序列中不使用該數值
- 在賦值中覆寫變數中的數值
- 在寫入 *e1 = e2 時透過引用覆寫數值。
如果一個數值具有 drop,則該數值內包含的所有數值也都具有 drop。
store
store 能力允許具有此能力的類型的數值存在於存儲中的某個數值內,但_不一定_是作為存儲中的頂層數值。這是唯一不直接限制操作的能力。相反,它與 key 配合使用時限制了在存儲中的存在。
如果一個數值具有 store,則該數值內包含的所有數值也都具有 store。
對於 Sui 而言,store 具有雙重職責。它控制哪些數值可以出現在 物件 內部,以及哪些物件可以被傳輸到其定義模組之外。
key
key 能力允許該類型充當 Move 部署所定義的存儲操作的鍵。雖然它因 Move 實例而異,但它用於限制所有存儲操作,因此為了將某個類型與存儲原語 (primitives) 一起使用,該類型必須具有 key 能力。
如果一個數值具有 key,則該數值中包含的所有數值都必須具有 store。這是唯一具有這種不對稱性的能力。
對於 Sui 而言,key 被用來表示一項 物件 (Object)。
內建類型 (Builtin Types)
所有原始內建類型都具有 copy、drop 和 store 能力。
- bool、u8、u16、u32、u64、u128、u256 以及 address 全都具有 copy、drop 和 store。
- vector<T> 可能具有 copy、drop 和 store,具體取決於 T 的能力。
- 詳情請參閱條件能力與泛型類型。
- 不可變引用 & 和可變引用 &mut 全都具有 copy 和 drop。
- 這指的是複製和丟棄引用本身,而不是它們所指向的內容。
- 引用不能出現在全域存儲中,因此它們不具有 store。
請注意,原始類型都不具備 key 能力,這意味著它們都不能直接與存儲操作一起使用。
標記結構體與列舉
要宣告 struct 或 enum 具有某種能力,可以在資料類型名稱之後、欄位/變體之前或之後使用 has <ability>。例如:
public struct Ignorable has drop { f: u64 }
public struct Pair has copy, drop, store { x: u64, y: u64 }
public struct MyVec(vector<u64>) has copy, drop, store;
public enum IgnorableEnum has drop { Variant }
public enum PairEnum has copy, drop, store { Variant }
public enum MyVecEnum { Variant } has copy, drop, store;
在這種情況下:Ignorable* 具有 drop 能力。Pair* 和 MyVec* 都具有 copy、drop 和 store。
所有這些能力都對這些受限操作提供了強大的保證。只有在數值具備該能力時,才能對其執行該操作;即使該數值深深巢狀在某些其他集合內部也是如此!
因此:在宣告結構體的能力時,對欄位有一定的要求。所有欄位都必須滿足這些約束。這些規則是必要的,以便結構體滿足上述能力的連通性規則。如果一個結構體宣告具備以下能力...
- copy:所有欄位都必須具備 copy。
- drop:所有欄位都必須具備 drop。
- store:所有欄位都必須具備 store。
- key:所有欄位都必須具備 store。
- key 是目前唯一不需要自身具備相同能力的。
列舉可以具備除了 key 以外的任何能力,列舉不能具備 key 是因為它們不能作為存儲中的頂層數值(物件)。不過,對於列舉變體的欄位,同樣適用於結構體欄位的規則。特別是,如果一個列舉宣告具備以下能力...
- copy:所有變體的所有欄位都必須具備 copy。
- drop:所有變體的所有欄位都必須具備 drop。
- store:所有變體的所有欄位都必須具備 store。
- key:如前所述,列舉不允許使用此能力。
例如:
// 一個沒有任何能力的結構體
public struct NoAbilities {}
public struct WantsCopy has copy {
f: NoAbilities, // ERROR 'NoAbilities' 不具備 'copy'
}
public enum WantsCopyEnum has copy {
Variant1,
Variant2(NoAbilities), // ERROR 'NoAbilities' 不具備 'copy'
}
同樣地:
// 一個沒有任何能力的結構體
public struct NoAbilities {}
public struct MyData has key {
f: NoAbilities, // Error 'NoAbilities' 不具備 'store'
}
public struct MyDataEnum has store {
Variant1,
Variant2(NoAbilities), // Error 'NoAbilities' 不具備 'store'
}
條件能力與泛型類型 (Conditional Abilities and Generic Types)
當在泛型類型上標記能力時,並非該類型的所有實例都保證具備該能力。考慮這個結構體宣告:
public struct Cup<T> has copy, drop, store, key { item: T }
如果 Cup 可以容納任何類型,無論其能力如何,這將非常有幫助。類型系統可以「看到」類型參數,因此如果它「看到」一個會違反該能力保證的類型參數,它應該能夠移除 Cup 的能力。
這種行為起初聽起來可能有點令人困惑,但如果我們考慮集合類型,可能會更容易理解。我們可以認為內建類型 vector 具有以下類型宣告:
vector<T> has copy, drop, store;
我們希望 vector 能夠與任何類型配合使用。我們不希望針對不同能力使用不同的 vector 類型。那麼我們想要什麼規則呢?正是我們在上面欄位規則中所想要的。因此,只有在內部元素可以被複製時,複製 vector 數值才是安全的。只有在內部元素可以被忽略/丟棄時,忽略 vector 數值才是安全的。並且,只有在內部元素可以存在於存儲中時,將 vector 放入存儲才是安全的。
為了擁有這種額外的表現力,一個類型可能不具備它宣告時的所有能力,這取決於該類型的實例化;相反,一個類型將具備的能力取決於其宣告 以及 其類型參數。對於任何類型,類型參數都會被悲觀地假設在結構體內部使用,因此只有在類型參數滿足上述欄位要求時,才會授予能力。以上面的 Cup 為例:
- 只有當 T 具備 copy 時,Cup 才具備 copy 能力。
- 只有當 T 具備 drop 時,它才具備 drop。
- 只有當 T 具備 store 時,它才具備 store。
- 只有當 T 具備 store 時,它才具備 key。
以下是針對每種能力的此類條件系統示例:
示例:條件 copy
public struct NoAbilities {}
public struct S has copy, drop { f: bool }
public struct Cup<T> has copy, drop, store { item: T }
fun example(c_x: Cup<u64>, c_s: Cup<S>) {
// 有效,'Cup<u64>' 具備 'copy' 因為 'u64' 具備 'copy'
let c_x2 = copy c_x;
// 有效,'Cup<S>' 具備 'copy' 因為 'S' 具備 'copy'
let c_s2 = copy c_s;
}
fun invalid(c_account: Cup<signer>, c_n: Cup<NoAbilities>) {
// 無效,'Cup<signer>' 不具備 'copy'。
// 儘管 'Cup' 宣告具備 copy,但該實例不具備 'copy',
// 因為 'signer' 不具備 'copy'
let c_account2 = copy c_account;
// 無效,'Cup<NoAbilities>' 不具備 'copy',
// 因為 'NoAbilities' 不具備 'copy'
let c_n2 = copy c_n;
}
示例:條件 drop
public struct NoAbilities {}
public struct S has copy, drop { f: bool }
public struct Cup<T> has copy, drop, store { item: T }
fun unused() {
Cup<bool> { item: true }; // 有效,'Cup<bool>' 具備 'drop'
Cup<S> { item: S { f: false }}; // 有效,'Cup<S>' 具備 'drop'
}
fun left_in_local(c_account: Cup<signer>): u64 {
let c_b = Cup<bool> { item: true };
let c_s = Cup<S> { item: S { f: false }};
// 有效回傳:'c_account', 'c_b', 與 'c_s' 都有數值
// 但 'Cup<signer>', 'Cup<bool>', 與 'Cup<S>' 都具備 'drop'
0
}
fun invalid_unused() {
// 無效,不能忽略 'Cup<NoAbilities>' 因為它不具備 'drop'。
// 儘管 'Cup' 宣告具備 'drop',但該實例不具備 'drop',
// 因為 'NoAbilities' 不具備 'drop'
Cup<NoAbilities> { item: NoAbilities {} };
}
fun invalid_left_in_local(): u64 {
let n = Cup<NoAbilities> { item: NoAbilities {} };
// 無效回傳:'c_n' 有數值
// 且 'Cup<NoAbilities>' 不具備 'drop'
0
}
示例:條件 store
public struct Cup<T> has copy, drop, store { item: T }
// 'MyInnerData 宣告具備 'store',所以所有欄位都需要 'store'
struct MyInnerData has store {
yes: Cup<u64>, // 有效,'Cup<u64>' 具備 'store'
// no: Cup<signer>, 無效,'Cup<signer>' 不具備 'store'
}
// 'MyData' 宣告具備 'key',所以所有欄位都需要 'store'
struct MyData has key {
yes: Cup<u64>, // 有效,'Cup<u64>' 具備 'store'
inner: Cup<MyInnerData>, // 有效,'Cup<MyInnerData>' 具備 'store'
// no: Cup<signer>, 無效,'Cup<signer>' 不具備 'store'
}
示例:條件 key
public struct NoAbilities {}
public struct MyData<T> has key { f: T }
fun valid(addr: address) acquires MyData {
// 有效,'MyData<u64>' 具備 'key'
transfer(addr, MyData<u64> { f: 0 });
}
fun invalid(addr: address) {
// 無效,'MyData<NoAbilities>' 不具備 'key'
transfer(addr, MyData<NoAbilities> { f: NoAbilities {} });
// 無效,'MyData<NoAbilities>' 不具備 'key'
borrow<NoAbilities>(addr);
// 無效,'MyData<NoAbilities>' 不具備 'key'
borrow_mut<NoAbilities>(addr);
}
// 模擬存儲操作
native public fun transfer<T: key>(addr: address, value: T);