前言
世界杯终于结束了,也要慢慢调回中国的作息了,不能再继续划水了,最近也看到Aptos在2月份将会在韩国举行黑客松,这也是一个练手的好机会,还是要赶紧学完Move,今天这篇文章将会主要介绍Move中的泛型,泛型对于Move来说十分重要,其令Move在区块链世界的地位十分特殊,也是Move灵活性的源泉。
泛型是对于具体类型和属性的抽象存在,从应用的角度来说,使用泛型可以通过写一个函数用于其他所有类型,它们也可以被称为这个模版,因为这个函数可以被当作模版处理其他任何类型,在Move中泛型可以用于结构体和函数。
结构体的定义
如果我们想创建一个Box的结构体,里面包含了一个u64的值,我们之前的做法是这样的:
module M {
struct Box {
value: u8
}
}
显然这个Box只能包含一个u64的值,但是如果我嘛需要一个包含u8或者bool类型的Box,我们该怎么做呢,创建一个Box1?或者Box2?当需要的类型越来越多时这种方法显的十分的繁琐,我们可以通过泛型来简化这个过程。
module M {
struct Box<T> {
value: T
}
}
可以看到在结构体旁边我们使用了'',其中的T就是我们在结构体里使用的类型,在结构体里我们可以将T当作常规类型使用,实际上T并不是一个存在的类型,其只是其他任何类型的占位符。
函数签名
现在我们为之前的结构体创建一个构造函数,首先使用之前熟悉的u64,最后的结果如下:
module M {
struct Box<T> {
value: T
}
//我们使用了u64替换了T意味着我们将会使用带有u64的结构体
public fun create_box(value: u64): Box<u64> {
Box<u64>{ value }
}
}
如上所示我们通过用u64替换T指定Box是带有u64的Box,但是有时候我们希望获取带有u8或者bool或者其他类型的Box,我们该怎么做呢,当然还是使用泛型,通过在函数定义时使用泛型,我们就可以实现这种效果,代码如下:
module M {
struct Box<T> {
value: T
}
public fun create_box<T>(value: T): Box<T> {
Box<T>{ value }
}
}
正如我们在结构体中使用泛型,在函数定义中使用泛型也是在函数名后加上'',当函数定义好后我们又该如何通过指定类型来调用函数呢。
script {
use {{sender}}::Storage;
use 0x1::Debug
fun main() {
//我们将会获得一个带有bool类型的Box
let bool_box = Storage::create_box<bool>(true);
let bool_val = Storage::value(&bool_box)
assert(bool_val, 0);
//我们也可以同样获取integer的Box
let u64_box = Storage::create_box<u64>(1000000);
let _ = Storage::value(&u64_box);
}
}
以上我们创建来带有u64和bool类型的Box,我们也可以使用泛型定义更加复杂的类型。
能力检查
在之前的文章中我们已经学过了Move中的四大能力,这在泛型中也是相同的。
fun name<T: copy>() {} // 只可以复制
fun name<T: copy + drop>() {} // 可以复制和丢弃
fun name<T: key + store + drop + copy>() {} // 四种能力都具备
在结构体中也是一样的
struct name<T: copy + drop> { value: T } // T可以复制和丢弃
struct name<T: store> { value: T } // T 可以全局存储
❝+的这种用法可能咋看起来有点不大好理解,这也是+关键字列表的唯一用法。
❞
其常规用法如下:
module Storage {
// box的contents可以被存储
struct Box<T: store> has key, store {
content: T
}
}
需要注意的是泛型类型必须要有其容器(如Box)拥有的所有能力(key除外)。其逻辑也很好理解,拥有copy能力的结构体,其内部的内容也必须要拥有copy能力,否则其就不能认为拥有copy能力。编译器无法发现这个问题,但是当你使用的时候会出错。例子如下
module Storage {
// 结构体无法复制和丢弃
struct Error {}
// 没有明确限制
struct Box<T> has copy, drop {
contents: T
}
// 这个方法创建来一个没有复制和丢弃能力内容的Box
public fun create_box(): Box<Error> {
Box { contents: Error {} }
}
}
这时编译不会有问题,但是当我们使用时
script {
fun main() {
{{sender}}::Storage::create_box() // value 被创建然后丢弃
}
}
会发生以下报错
┌── scripts/main.move:5:9 ───
│
5 │ Storage::create_box();
│ ^^^^^^^^^^^^^^^^^^^^^ Cannot ignore values without the 'drop' ability. The value must be used
│
这个错误发生是因为内部的值没有drop能力,所以外部的容器也会被限制没有drop能力。
安全的结构体的定义方式如下:
struct Box<T: copy + drop> has copy, drop {
contents: T
}
多个类型的泛型
多个类型的泛型与单个泛型类似,不同的是不同泛型使用逗号隔开。
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
}
}
}
多个泛型的使用也类似
script {
use {{sender}}::Storage;
fun main() {
let b1 = Storage::create_box<u64>(100);
let b2 = Storage::create_box<u64>(200);
let _ = Storage::create_shelf<u64, u64>(b1, b2);
}
}
需要注意的是不是所有制定类型的泛型都要使用,例子如下
module Storage {
struct Abroad {}
struct Local {}
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);
}
}
最后
这篇文章主要介绍了Move中泛型。更多文章可以关注微信公众号QStack。