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);
    }
}
Subscribe to shaneson.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.