Move基本数据类型和数组

语言嘛,其实都差不多。如果有其他高级语言的编程经验,比如Python、Java、Golang、Rust等等,那么你可以跨过本期内容了。真的没啥可讲的。

Move主要有3类基本数据类型:

  • 整型,其中整型包括 (u8u64u128)
  • 布尔型 boolean
  • 地址 address

跟其他语言对比,Move的基本数据类型缺少两种类型:

  • Move没有字符串类型(更正一下,最新Move已经支持string类型。从定义中我们可以看到存了一个UTF-8的二进制数组)
    • Move没有浮点型(Solidity也没有浮点型,这个其实简单,用整数来代替就可以了),所以在各大Move公链的Framework或者Stdlib中都有一个FixedPoint32.move的模块

    另外,Move的基本数据类型还有3个点需要注意的地方:

    • 没有负数(这个其实也简单,用一个bool型表示符号就可以了)
    • 没有u256类型(这个在DeFi里面很重要,记得当时Starcoin跟Libra的Move社区讨论过很久,一直没有通过,最后选择在u128上面进行封装)
    • address虽然是专门的地址类型,但是本质上是整型,所以address能跟整型相互转换(Solidity也是这样的)

    这是Move的基本数据类型跟其他高级语言不同的地方。

    除了上面提到的基本数据类型,数组也是最常用的数据类型,所以这里把数组也一起讲了。Move的数组类型是vector。关于vector类型有3个需要说明的地方:

    • vector是泛型类型(以后再讲什么是泛型),例如vector<u8>或者vector<address>等等
    • vector是类型,而std::vector是一个Move模块(不是Move语言本身的),std::vector是一些用来操作vector类型数据的函数。所以一定要注意,vector和std::vector虽然有关联,但不是一个东西
    • 在Move官方版本中,操作vector类型的是std::vector,而在一些公链的Stdlib中则是std::Vector,这个也需要格外注意一下

    定义Move变量和常量

    我们这里以Move官方版本为准,使用上面介绍的这些基本数据类型和数组定义Move变量。

    Move是mini版的Rust(开玩笑),Move借鉴了很多Rust的思想。Move在定义变量的时候,也跟Rust一样,使用了let关键字。

    1. 整型变量
    //变量定义
    let _a:u8 = 1;
    let _b: u8;
    _b = 10;
    let c = 2u64;
    
    // 类型强转
    let _d = c as u128;
    let _e = c as u8;

    上面既介绍了整型变量的定义方式,也介绍了如何类型强转。

    1. 布尔型变量
    let _flag = true;
    1. 地址变量
     let _addr1: address;
     _addr1 = @test_addr;
    
    let _addr2 = 0x1;// 确定的地址

    这里的地址类型需要注意一下:test_address是在Move.toml文件里面定义好的地址,通过@引用。

    image-20220821100344880

    1. 数组类型
    let _hello_world = b"hello_world";// 字符串会根据ASCII转换成u8数组
    let _boxes: vector<u64> = std::vector::empty<u64>();// 通过std::vector定义vector<u64>类型的数组

    注意一下,数组类型的两个例子很有代表性。

    1. 常量
    const ADDR: address = @test_addr;
    const FASLE: bool = false;

    常量是通过const关键字定义的,必须放在方法外面定义。

    完整代码

    我们看一下上面这些例子的完整代码:

    script {
        const ADDR: address = @test_addr;
        const FASLE: bool = false;
    
        fun main() {
            let _a:u8 = 1;
            let _b: u8;
            _b = 10;
            let c = 2u64;
    
            let _d = (c as u128);
            let _e = (c as u8);
    
            let _flag = true;
    
            let _addr1: address;
            _addr1 = @test_addr;
    
            let _addr2 = 0x1;
    
            let _hello_world = b"hello_world";
            let _boxes: vector<u64> = std::vector::empty<u64>();
        }
    }

    这里注意一下变量前面的下划线_,如果定义的变量在后面没有被使用到,编译的时候会抛错,使用_表示你知道后面没被使用,在内存中可以快速回收掉,编译器在编译的时候不会报错。这也是Rust的风格。

    最后,编译一下代码:

    move build

https://github.com/tiangong3624749/LearnMove/blob/main/variable.move.md

第一个Move合约

module hello_world::counter {
    // Part 1: imports
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    // Part 2: struct definition
    struct Counter has key {
        id: UID,
        value: u64,
    }

    // Part 3: transfer the counter object to the sender
    entry public fun getCounter(ctx: &mut TxContext) {
        // sender address
        let sender = tx_context::sender(ctx);
        let counter_obj = Counter {
            id: object::new(ctx),
            value: 0
        };
        transfer::transfer(counter_obj, sender);
    }

    // part 4: public/ entry functions
    public entry fun incr(counter: &mut Counter) {
        counter.value = counter.value + 1;
    } 
}

来源 https://blog.chrisyy.top/hello-sui-1.html

此合约的功能是一个简单的计数器合约:

用户可以调用getCounter获得一个Counter计数器对象
用户可以调用并且能够让他的value值+1
让我们分解这段代码为四个不同部分:

Part1导入:这些允许我们的模块使用在其他模块中声明的类型和函数。在此模块中,我们从三个不同的模块中引入导入,分别是transfer,object,tx_context,其作用我们在下文解释。
Part2结构声明:这里定义了可以由该模块创建/销毁的类型。这里的key 表明这些结构是作为全局索引资源。store能力允许它存储在其他结构的字段中并可以自由转移。
Part3getCounter函数:向调用者转入一个Counter对象
Part4incr函数:传入一个对象的可变引用,并对其value字段+1
合约一开始引入了transfer库,transfer函数有两个参数一个是被对象,二是接受者地址,能够转让一个对象的所有权给另一个地址,同时这个对象必须有一个全局独一无二的ID也就是Counter中的id字段,这个ID就是由object中的new函数生成,new函数接受当前交易的上下文作为参数,从而生成一个独一无二的ID。

编写完成之后,我们需要位于项目的根目录,然后使用sui move build来编译它。

开发实践中遇到的问题
u64_to_string
方法1:
使用aptos包
aptos-core/aptos-move/framework/aptos-stdlib/sources
/string_utils.move

方法2:

fun u64_to_string(value: u64): string::String {
        if (value == 0) {
            return string::utf8(b"0")
        };
        let buffer = vector::empty();
        while (value != 0) {
            vector::push_back(&mut buffer, ((48 + value % 10) as u8));
            value = value / 10;
        };
        vector::reverse(&mut buffer);
        string::utf8(buffer)
    }

参考 https://github.com/DreamXzxy/NFTR/blob/567bd9837b2e3cd71d94cb22818a0afbc139b18f/sources/NFTR.move#L105

package与object权限
通过测试发现box创建出的obj必须由owner修改,多个hello_world的box无法互相修改。会报错 { arg_idx: 0, kind: TypeMismatch }

module hello_world::typetest {
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{TxContext, sender};

    struct Box has key, store {
        id: UID,
        value: u64
    }

    public entry fun create_box(ctx: &mut TxContext) {
        let box = Box {
            id: object::new(ctx),
            value: 0
        };
        transfer::public_transfer(box, sender(ctx))
    }

    public entry fun change_id(box: &mut Box, newval: u64) {
        box.value = newval
    }
}