Move高级进阶--泛型编程

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: Abilities限制符 泛型里的模版参数也是有限制的,所以可以在模版定义的时候定义它的权限:“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); } }