shaneson.eth

Posted on Aug 24, 2022Read on Mirror.xyz

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);
    }
}