背景
之前的文章我们已经介绍了Move的模块和函数,接下来将会介绍一些Move的特性。
结构体
结构体是用户自定义的拥有复杂数据的数据类型,它可以简单理解为一个key——value形式的存储器,key时存储数据的名字而value是其存储的值。
结构体的定义
结构体只能在模块里定义,一struct关键字开头,之后时名字和双括号,双括号里面是是定义:
struct Name {
FIELD1: TYPE1,
FIELD@: TYPE2,
......
}
以下就是结构体定义的例子:
module M {
struct Empty{}
struct MyStruct{
field1: address,
field2: bool,
field3: Empty
}
struct Example {
field1: u8,
field2: address,
field3: u64,
field4: bool,
field5: bool,
field5: MyStruct
}
}
需要注意的是每一个定义的结构体都会变成一个新的类型,这个类型可以桶模块访问,如M::MyStruct。
递归定义在Move是不允许的,例如以下代码编译会报错:
struct MyStruct{
field5: MyStruct
}
结构体的使用
我们可以通过创建实例来使用结构体,可以通过它的定义来创建实例:
module Country {
struct Country {
id: u8,
population: u64
}
public fun new_country(c_id: u8, c_population: u64): Country {
let country = Country {
id: c_id,
population: c_population
};
country
}
}
Move还允许使用更简洁的方式创建实例:
// ...
public fun new_country(id: u8, population: u64): Country {
// id matches id: u8 field
// population matches population field
Country {
id,
population
}
// or even in one line: Country { id, population }
}
访问结构体的属性
只有模块内部才能访问结构体的属性,对于模块外部,结构体的属性是不可见的:
// ...
public fun get_country_population(country: Country): u64 {
country.population // <struct>.<property>
}
结构结构体
可以通过let = 去解构一个结构体
module Country {
// ...
// we'll return values of this struct outside
public fun destroy(country: Country): (u8, u64) {
// variables must match struct fields
// all struct fields must be specified
let Country { id, population } = country;
// after destruction country is dropped
// but its fields are now variables and
// can be used
(id, population)
}
}
需要注意的是未使用的变量在Move中是被禁止的,如果解构结构体又不需要使用其属性,需要使用下划线:
module Country {
// ...
public fun destroy(country: Country) {
// this way you destroy struct and don't create unused variables
let Country { id: _, population: _ } = country;
// or take only id and don't init `population` variable
// let Country { id, population: _ } = country;
}
}
之前提到外部的模块是无法直接访问结构体的属性的,所以为了外部模块能够访问定义的结构体的属性,需要提供相关的访问函数:
module Country {
struct Country {
id: u8,
population: u64
}
public fun new_country(id: u8, population: u64): Country {
Country {
id, population
}
}
// don't forget to make these methods public!
public fun id(country: &Country): u8 {
country.id
}
// don't mind ampersand here for now. you'll learn why it's
// put here in references chapter
public fun population(country: &Country): u64 {
country.population
}
// ... fun destroy ...
}
Abilities
Move有一个非常灵活且可高度定制化的类型系统,每一个类型都可以被赋予四种能力去决定数据怎么被使用、删除活存储,这四种能力如下:
-
Copy-可以被复制
-
Drop-可以在结尾被删除
-
Key-可以被全局存储设置为key
-
Store-可以被全局存储
这篇文章会介绍Copy和Drop的能力,至于Key和Store能力在之后的文章详细介绍。在定义结构体的时候可以设置这四种能力:
module Library { struct Book has store, copy, drop { year: u64 } struct Storage has { books: vector<Book> } struct Empty{}
}
如果没有设置这几种属性会怎么样呢:
odule Country {
struct Country {
id: u8,
population: u64
}
public fun new_country(id: u8, population: u64) {
Country(id, population)
}
}
script {
use {{sender}}::Country;
fun main() {
Country::new_country(1, 1000000);
}
}
我嘛在Country中定义了一个结构体,然后通过一个方法暴露给外部,然后在外部调用这个方法来获取数据,但是返回值没有被使用,由于我们没有给这个结构体设置Drop属性,这时候不会自动丢弃返回值,会报错
error:
┌── scripts/main.move:5:9 ───
│
5 │ Country::new_country(1, 1000000);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot ignore values without the ‘drop’ ability. The value must be used
│
#### Drop
我们可以通过has Drop给结构体设置Drop能力
module Country {
struct Country has drop { // has
id: u8,
population: u64
}
// …
}
当我们设置了Drop能力,脚本就能运行了:
script {
use {{sender}}::Country;
fun main() {
Country::new_country(1, 1000000); // value is dropped
}
}
> Drop只定义Drop的能力,解构不需要Drop能力。
#### Copy
我们已经学习了如何创建一个Country实例以及丢弃它,那么我们该如何实现拷贝呢,Move提供来copy关键字来实现拷贝,前提是需要已经赋予结构体copy能力。
module Country {
struct Country has drop, copy { // see comma here!
id: u8,
population: u64
}
// …
}
script {
use {{sender}}::Country;
fun main() {
let country = Country::new_country(1, 1000000);
let _ = copy country;
}
}
### 最后,这篇文章我们主要介绍了Move的结构体与类型系统的四种能力,更多文章可以关注QStack。