了解泛型

泛型对于 Move 语言是必不可少的,它使得 Move 语言在区块链世界中如此独特,它是 Move 灵活性的重要来源。

首先,让我来引用 Rust Book 对于泛型得定义:泛型是具体类型或其他属性的抽象替代品。实际上,泛型允许我们只编写单个函数,而该函数可以应用于任何类型。这种函数也被称为模板 —— 一个可以应用于任何类型的模板处理程序。

Move 中泛型可以应用于结构体和函数的定义中。

结构体中的泛型

首先,我们将创建一个可容纳u64整型的 Box :

module book::generics { public struct Box { value: u64 } }

这个 Box 只能包含u64类型的值,这一点是非常清楚的。但是,如果我们想为u8类型或 bool类型创建相同的 Box 该怎么办呢?分别创建u8类型的 Box1 和bool型 Box2 吗?答案是否定的,因为可以使用泛型。

module book::generics { public struct Box<T> { value: T } }

我们在结构体名字的后面增加<T>。尖括号<..>里面用来定义泛型,这里T就是我们在结构体中模板化的类型。在结构体中,我们已经将T用作常规类型。类型T实际并不存在,它只是任何类型的占位符。

函数中的泛型

现在让我们为上面的结构体创建一个构造函数,该构造函数将首先使用u64类型。

module book::generics { public struct Box<T> { value: T } // type u64 is put into angle brackets meaning // that we're using Box with type u64 public fun create_box(value: u64): Box<u64> { Box<u64>{ value } } }

带有泛型的结构体的创建稍微有些复杂,因为它们需要指定类型参数,需要把常规结构体 Box 变为 Box。Move没有任何限制什么类型可以被放进尖括号中。但是为了让create_box方法更通用,有没有更简单的方法?有的,在函数中使用泛型!

module book::generics { // ... public fun create_box<T>(value: T): Box<T> { Box<T> { value } } // we'll get to this a bit later, trust me public fun value<T: copy>(box: &Box<T>): T { *&box.value } }

函数调用中使用泛型

上例中在定义函数时,我们像结构体一样在函数名之后添加了尖括号。如何使用它呢?就是在函数调用中指定类型。

module book::generics { use {{sender}}::storage; use std::debug; fun main() { // value will be of type storage::Box<bool> let bool_box = storage::create_box<bool>(true); let bool_val = storage::value(&bool_box); assert(bool_val, 0); // we can do the same with integer let u64_box = storage::create_box<u64>(1000000); let _ = storage::value(&u64_box); // let's do the same with another box! let u64_box_in_box = storage::create_box<storage::Box<u64>>(u64_box); // accessing value of this box in box will be tricky :) // Box<u64> is a type and Box<Box<u64>> is also a type let value: u64 = storage::value<u64>( &storage::value<storage::Box<u64>>( // Box<u64> type &u64_box_in_box // Box<Box<u64>> type ) ); // you've already seed debug::print<T> method // which also uses generics to print any type debug::print<u64>(&value); } }

这里我们用三种类型使用了 Box:bool, u64Box<u64>。最后一个看起来有些复杂,但是一旦你习惯了,并且理解了泛型是如何工作的,它成为你日常工作的好帮手。

继续下一步之前,让我们做一个简单的回顾。我们通过将泛型添加到Box结构体中,使Box变得抽象了。与 Box 能提供的功能相比,它的定义相当简单。现在,我们可以使用任何类型创建Box,u64 或 address,甚至另一个 Box 或另一个结构体。

abilities 限制符

我们已经学习了 abilities,它们可以作为泛型的限制符来使用,限制符的名称和 ability 相同。

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

也可以在结构体泛型参数中使用:

public struct name<T: copy + drop> { value: T } // T can be copied and dropped public struct name<T: stored> { value: T } // T can be stored in global storage

请记住 + 这个语法符号,第一眼看上去可能不太适应,因为很少有语言在关键字列表中使用 +

下面是一个使用限制符的例子:

module book::generics { // contents of the box can be stored public struct Box<T: store> has key, store { content: T } }

另一个需要被提及的是结构体的成员必须和结构体具有相同的 abilities (除了key以外)。这个很容易理解,如果结构体具有 copy ability,那么它的成员也必须能被 copy,否则结构体作为一个整体不能被 copy。Move 编译器允许代码不遵守这样的逻辑,但是运行时会出问题。

module book::generics { // non-copyable or droppable struct public struct Error {} // constraints are not specified public struct Box<T> has copy, drop { contents: T } // this method creates box with non-copyable or droppable contents public fun create_box(): Box<Error> { Box { contents: Error {} } } }

这段代码可以成功编译和发布,但是如果你运行它就会出问题。

module book::generics { fun main() { {{sender}}::storage::create_box() // value is created and dropped } }

运行结果是报错 Box 不能被 drop。

┌── scripts/main.move:5:9 ─── │ 5 │ storage::create_box(); │ ^^^^^^^^^^^^^^^^^^^^^ Cannot ignore values without the 'drop' ability. The value must be used │

原因是创建结构体时所使用的成员值没有 drop ability。也就是 contents 不具备 Box 所要求的 abilities - copy 和 drop。

但是为了避免犯错,应该尽可能使泛型参数的限制符和结构体本身的 abilities 显式的保持一致。

所以下面这种定义的方法更安全:

// we add parent's constraints // now inner type MUST be copyable and droppable public struct Box<T: copy + drop> has copy, drop { contents: T }

泛型中包含多个类型

我们也可以在泛型中使用多个类型,像使用单个类型一样,把多个类型放在尖括号中,并用逗号分隔。我们来试着添加一个新类型Shelf,它将容纳两个不同类型的Box

module book::generics { public struct Box<T> { value: T } public 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的类型参数需要与结构体字段定义中的类型顺序相匹配,而泛型中的类型参数的名称则无需相同,选择合适的名称即可。正是因为每种类型参数仅仅在其作用域范围内有效,所以无需使用相同的名字。

多类型泛型的使用与单类型泛型相同:

module book::generics { 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); } }

*你可以在函数或结构体定义中最多使用 18,446,744,073,709,551,615 (u64 最大值) 个泛型。你绝对不会达到此限制,因此可以随意使用。

未使用的类型参数

并非泛型中指定的每种类型参数都必须被使用。看这个例子:

module book::generics { // these two types will be used to mark // where box will be sent when it's taken from shelf public struct Abroad {} public struct Local {} // modified Box will have target property public struct Box<T, Destination> { value: T } public fun create_box<T, Dest>(value: T): Box<T, Dest> { Box { value } } }

也可以在脚本中使用 :

module book::generics { 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); } }