下一代智能合约编程语言Move(四)

2,376 阅读4分钟

背景

之前的文章我们已经介绍了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。