[Aptos學習筆記#6]Move進階使用 - 了解泛型 - Understanding Generics
一. 了解泛型 - Understanding Generics
泛型在Move語言中是非常重要的,它讓Move在區塊鏈中很獨特,它是 Move 靈活性的重要來源
二. 泛型是什麼?
- RUST中定義: 泛型是具體類型或是其他屬性的抽象替代品
- 泛型讓大家可以僅編寫單個函數,而該函數可以應用於任何類型
- 此類型的函數被稱為模板: 一個可以應用於任何類型的模板處理程序
- Move中的泛型可以應用於結構體和函數的定義之中
三. 結構體中定義的泛型
創建一個容納u8類型值的Box
module Storage {
struct Box {
value: u8
}
}
很明顯這個Box只能包含u8類型的值,但是如果想要為u64類型或是bool類型創建相同的Box怎麼辦?
總不能為此創建u64類型的Box1和bool類型的Box2吧,這邊我們就可以使用泛型
module Storage {
struct Box<T> {
value: T
}
}
這邊在結構體名字後面增加<T>,<…>裡面用來定義泛型,這邊T就是在結構體中模板化的類型,我們將T用作常規類型
其實類型T實際上並不存在,它是任何類型的佔位符
四. 函數中的泛型 - In function signature
這邊我為這個結構創建一個構造函數,並以u64類型作為值
module Storage {
// 構建一個泛型的結構體Box
struct Box<T> {
value: T
}
// 這邊<U64>被放進Box結構體中表示我們要使用類型為u64的Box結構體
public fun create_box(value: u64): Box<u64> {
Box<u64>{ value }
}
}
如上帶有泛型的結構體定義起來有點複雜,因為我們需要指定它們的類型參數,像是如上範例需要把常規結構體的Box變成Box<u64>
上面這樣寫起來太麻煩,雖然Move中沒有限制<>中需要放什麼類型,但為了讓create_box方法更通用,有沒有更簡易的方法呢?
當然有,就是在函數中也使用泛型
module Storage {
// 構建一個泛型的結構體Box
struct Box<T> {
value: T
}
// 在構建的函數中使用泛型
public fun create_box<T>(value: T): Box<T> {
Box<T> { value }
}
// 利用泛型函數獲得結構體中的值
public fun value<T: copy>(box: &Box<T>): T {
*&box.value
}
}
五. 函數中調用泛型
在Script中使用如何使用前面在Module中構建的泛型函數呢? 在函數調用中指定其類型即可
script {
use {{sender}}::Storage;
use 0x1::Debug;
fun main() {
// 利用泛型函數來創建一個bool類型的Box結構體
let bool_box = Storage::create_box<bool>(true);
// 取得bool_box(Box結構體)的值
let bool_val = Storage::value(&bool_box);
assert(bool_val, 0);
// 利用泛型函數來創建一個u64類型的Box結構體
let u64_box = Storage::create_box<u64>(1000000);
// 取得u64_box(Box結構體)的值
let _ = Storage::value(&u64_box);
// 利用泛型函數來創建一個Box<u64>類型的Box結構體
let u64_box_in_box = Storage::create_box<Storage::Box<u64>>(u64_box);
// 要取得u64_box_in_box的值是非常有意思的
// Box<u64>是一種類型,而Box<Box<u64>>也是一種類型
// 這邊看起來用了兩層來取得該值
let value: u64 = Storage::value<u64>(
&Storage::value<Storage::Box<u64>>( // Box<u64> type
&u64_box_in_box // Box<Box<u64>> type
)
);
// 這邊我們已經能夠理解 Debug::print<T> 方法
// 它也可以透過泛型的方法印出任何類型
Debug::print<u64>(&value);
}
}
這邊使用了三種類型來構建Box: u64, bool 和 Box<u64>,Box<u64>看起來比較複雜,但是只要我們習慣了,並且能夠了解泛型是如何運作的,它會成為我們非常重要的幫手
重要筆記:
透過將泛型添加到Box的結構體中,讓Box變得更抽象了,與它給我們功能相比,這種泛型的結構體定義起來相當簡單
我們可以創建任何類型的Box結構體,像是address, bool或u64等來創建另一個Box結構體或任何其它結構體
六. abilities 限制符 - Contraints to check Abilities
前面的筆記中有提過了Move中的Abilities,它們一樣可以當成是泛型的限制符來使用,其使用的名稱和Abilities相同
**fun name<T: copy>() {} // 只允許值能夠被複製
fun name<T: copy + drop>() {} // 值可以被複製或是丟棄
fun name<T: key + store + drop + copy>() {} // 所有4項能力都能夠被表達**
也可以在結構體中的泛型參數使用
struct name<T: copy + drop> { value: T } **// T可以被複製和丟棄**
struct name<T: store> { value: T } **// T可以被儲存於全域中**
重要提醒: 記住+個符號,它可能沒有那麼直覺,Move是少數使用+在關鍵字列表的(Keyword List)
例子: 使用限制符的結構體(struct)
module Storage {
**// Box的內容可以被存儲於全域中**
struct Box<T: store> has key, store {
content: T
}
}
重要觀念: 結構體的成員必須與結構體具備相同的 Abilities (除了 key 以外)
舉例: 如果結構體具備 Copy 和 Drop 能力,那其成員也必須具備Copy 和 Drop,否則其成員不可以視為具備Copy和Drop能力
Move編譯器會讓我們在不遵守上述邏輯下通過,但是我們在運行的時候,如果需要使用此項能力會報錯喔
module Storage {
// 不具備Copy和Drop能力的結構體
struct Error {}
// 創建一個具備Copy和Drop的結構體
// 但其成員T並沒有寫下任何限制符
struct Box<T> has copy, drop {
contents: T
}
// 這個方法創建了包含無法複製和可丟棄內容的Box結構體
public fun create_box(): Box<Error> {
Box { contents: Error {} }
}
}
上面這個例子會編譯成功,但如果我們要在Script中執行它
script {
fun main() {
{{sender}}::Storage::create_box() // 值被創建後,接著就被丟棄
}
}
我們就會得到一個報錯的結果,告訴我們Box結構體不具備Drop的能力
┌── scripts/main.move:5:9 ─── │ 5 │ Storage::create_box(); │ ^^^^^^^^^^^^^^^^^^^^^ Cannot ignore values without the 'drop' ability. The value must be used │
報錯的原因在於我們創建結構體時所使用的成員值不具有Drop的能力,也就是Contents不具有Box所要求的Abilities - Copy 與 Drop
重要提醒: 為了避免這樣的錯誤發生,我們應該盡可能地讓泛型參數的 Abilities 限制符與結構體本身的 Abilities 顯示地保持一樣
例子: 在這次的例子中這樣的定義方式更加安全
// 這邊我們在父母結構體中加上限制符
// 這樣inner類型都要可以複製和丟棄了
struct Box<T: copy + drop> has copy, drop {
contents: T
}
七. 泛型中的多種類型 - Multiple types in generics
我們也可以在泛型中使用多種類型,如使用單個類型的方式一樣,把多個類型放在<>之中,並用,逗號隔開
例子: 我們加入新的結構體類型Shelf,它可以容納兩種類型的Box結構體
module Storage {
struct Box<T> {
value: T
}
struct Shelf<T1, T2> {
box_1: Box<T1>,
box_2: Box<T2>
}
public fun create_shelf<Type1, Type2>(
box_1: Box<Type1>,
box_2: Box<Type2>
): Shelf<Type1, Type2> {
Shelf {
box_1,
box_2
}
}
}
- Self的類型參數需與結構體中字段的定義類型順序相匹配
- 但泛型中的類型參數名稱就不需要相同,自行選擇合適的名稱就行了
- 每個類型的參數只有在定義範圍內有效,所以不需要將T1或T2和T1相匹配
例子: 執行多種泛型類型參數的方式與單個的相似
script {
use {{sender}}::Storage;
fun main() {
let b1 = Storage::create_box<u64>(100);
let b2 = Storage::create_box<u64>(200);
// 我們可以使用任何類型,這邊使用相同類型一樣有效
let _ = Storage::create_shelf<u64, u64>(b1, b2);
}
}
在u64類型這個定義中最多可以有 18,446,744,073,709,551,615 (u64的大小)個泛型,基本上我們可能不會用到這個上限,所以盡情使用不用擔心限制
八. 未被使用的類型參數 - Unused type params
並不是泛行中指定的每種類型參數都需要被使用到
module Storage {
// 這兩種類型將會被用於標記
struct Abroad {}
struct Local {}
// 更改後的Box將會具備目標屬性
struct Box<T, Destination> {
value: T
}
public fun create_box<T, Dest>(value: T): Box<T, Dest> {
Box { value }
}
}
可以在Script中被使用
script {
use {{sender}}::Storage;
fun main() {
// 此值將會是 Storage::Box<bool> 類型
let _ = Storage::create_box<bool, Storage::Abroad>(true);
let _ = Storage::create_box<u64, Storage::Abroad>(1000);
let _ = Storage::create_box<u128, Storage::Local>(1000);
let _ = Storage::create_box<address, Storage::Local>(0x1);
// 甚至可以使用u64類型當 Destination
let _ = Storage::create_box<address, u64>(0x1);
}
}
這邊我們使用泛型來標記類型,但實際上我們並沒有使用到它
後續當我們了解 Resource 的概念後,就會明白了它的重要性
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!







- 来自作者
- 相关推荐