Move语言里,最高级和最难掌握的用法是泛型。它是 Move 灵活性的重要来源,融合C++的泛型编程的思想。与Solidity对比,泛型的功能可以让工厂合约更容易实现合约模版,提供了非常强的灵活性。本文会介绍,我在学习Move泛型编程时的要点。
通过泛型的定义,我们可以使得value的值可以是任何的类型T。
module Storage {
struct Box<T> {
value: T
}
}
我以create Box为例,构造一个模版方法和get 方法。
module ShanesonStudy::Generics {
struct Box<T> has drop, copy, key {
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
}
}
create_box的模版参数为T,假设传入的值为value,类型为T。那么语法就是create_box (value: T),返回值是Box。这里值得注意的是,临时变量是要被丢弃的。所以,struct Box要有Drop权限。
方法value看起来非常复杂,意思就是:“就是把Box里面的value拷贝出来一份”。
测试方法:
module ShanesonStudy::GenericsTest {
use ShanesonStudy::Generics as G;
use std::debug as D;
#[test]
public entry fun create_box_test() {
let _box = G::create_box<bool>(true);
let _box_val = G::value(&_box);
D::print<bool> (&_box_val);
assert!(_box_val, 0);
let u64_box = G::create_box<u64> (1000000);
let _u64_box_result = G::value(&u64_box);
D::print<u64>(&_u64_box_result);
}
}
Result:
泛型里的模版参数也是有限制的,所以可以在模版定义的时候定义它的权限:“copy + drop”
fun name<T: copy>() {} // allow only values that can be copied
fun name<T: copy + drop>() {} // values can be copied and dropped
fun name<T: key + store + drop + copy>() {} // all 4 abilities are present
struct name<T: copy + drop> { value: T } // T can be copied and dropped
struct name<T: stored> { value: T } // T can be stored in global storage
注意,尽可能使泛型参数的限制符和结构体本身的abilities显示的保持一致。+号很少使用,但是这里确实是+号来拼接限制符。
所以下面这种定义的方法更安全:
// we add parent's constraints
// now inner type MUST be copyable and droppable
struct Box<T: copy + drop> has copy, drop {
contents: T
}
我们也可以在泛型中使用多个类型,像使用单个类型一样,把多个类型放在尖括号中,并用逗号分隔。我们来试着添加一个新类型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
}
}
}
Shelf
的类型参数需要与结构体字段定义中的类型顺序相匹配,而泛型中的类型参数的名称则无需相同,选择合适的名称即可。正是因为每种类型参数仅仅在其作用域范围内有效,所以无需使用相同的名字。
多类型泛型的使用与单类型泛型相同:
script {
use {{sender}}::Storage;
fun main() {
let b1 = Storage::create_box<u64>(100);
let b2 = Storage::create_box<u64>(200);
// you can use any types - so same ones are also valid
let _ = Storage::create_shelf<u64, u64>(b1, b2);
}
}
并非泛型中指定的每种类型参数都必须被使用。看这个例子:
module Storage {
// these two types will be used to mark
// where box will be sent when it's taken from shelf
struct Abroad {}
struct Local {}
// modified Box will have target property
struct Box<T, Destination> {
value: T
}
public fun create_box<T, Dest>(value: T): Box<T, Dest> {
Box { value }
}
}
也可以在脚本中使用 :
script {
use {{sender}}::Storage;
fun main() {
// value will be of type 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);
// or even u64 destination!
let _ = Storage::create_box<address, u64>(0x1);
}
}