Move语言学习笔记

993 阅读8分钟

前面已经实践过通过Aptos CLI编译、部署一个简单的Move合约到Aptos链上,接下来对Move编程语言进行基本用法的学习,学习内容参考The Move Book:aptos.dev/move/book/S…

本篇笔记不会完全记录Move语言的全部特性,而是选择其比较独特的部分进行重点记录。

更具体一点的学习,分为以下几部分:

  • 基本数据类型
  • 结构体与资源
  • 全局存储
  • 函数

基本数据类型

Move的基础类型有:

Integers, Bool, Address, Vector, Signer, References, Tuples and Unit

Integers

unsigned integer types: u8u16u32u64u128 ,u256

Vector

vector<T> 是 Move 提供的唯一原始集合类型。

Signer

  • Signer是Move的一个内建类型. Signer代表持有者代表一个地址行使权力的能力。
  • 和Address类型比较起来,Address可以不经任何许可就创建出来,比如用Aptos CLI就可以创建任意的address,但是Signer不能通过字面指令创建,而是由虚拟机创建的。
  • Signer的权限是被严格控制的,不可复制、存储,只有drop的能力。
  • 用法举例:
script {
    use std::signer;
    fun main(s: signer) {
        assert!(signer::address_of(&s) == @0x42, 0);
    }
}

References

Move语言有两种类型的引用:mutable引用和immutable引用

  • mutable

    • &mut:可变引用允许通过引用进行写入修改
  • immutable

    • &:只读,不能修改引用对象的值

语法如下:

SyntaxTypeDescription
&e&T where e: T and T is a non-reference typeCreate an immutable reference to e
&mut e&mut T where e: T and T is a non-reference typeCreate a mutable reference to e.
&e.f&T where e.f: TCreate an immutable reference to field f of struct e.
&mut e.f&mut T where e.f: TCreate a mutable reference to field f of structe.
freeze(e)&T where e: &mut TConvert the mutable reference e into an immutable reference.

根据引用进行读写操作:

SyntaxTypeDescription
*eT where e is &T or &mut TRead the value pointed to by e
*e1 = e2() where e1: &mut T and e2: TUpdate the value in e1 with e2.
  • 用到了类似C语言风格的 *语法,对mutable reference进行写入时,*e必须出现在等号的左边
  • 可读的引用,必须有copy ability;可写的引用,必须有drop的ability
  • reference是不能被存储的

Tuples和Unit

Tuple的出现是为了满足函数有多个返回值的需求,因此它只是出现在表达式中,换言之,它仅存在于源代码层面,并不存在于字节码层面。

  • 只能出现在函数的表达式中,通常是在返回值表达式中
  • 不能绑定到局部变量
  • 不能在Struct中存储
  • 不能用于实例化泛型
  • unit()可以看做空元组,不产生任何运行值
  • 适用于元祖的任何限制,也适用于unit
  • 唯一可以对tuples执行的操作是解构destructuring,对于任何大小的元组,它们可以在 let 绑定或赋值中被解构。

Tuples的定义:

SyntaxTypeDescription
()(): ()Unit, the empty tuple, or the tuple of arity 0
(e1, ..., en)(e1, ..., en): (T1, ..., Tn) where e_i: Ti s.t. 0 < i <= n and n > 0A n-tuple, a tuple of arity n, a tuple with n elements

结构体与资源

Struct

  • 结构体是包含类型字段的用户自定义数据结构,必须定义在Module内。
  • 结构体可以存储任何非引用类型,包括其他结构体,但不能有递归用法,比如:struct Foo { x: Foo }这种就是错误的用法
  • 结构体默认情况下是线性的、短暂的,但结合Abilities的使用可以扩展出更多的用法

Resource

  • 如果结构体不能复制也不能删除,我们经常将它们称为资源

Move语言中的Abilities

  • [copy] 复制

    • 允许此类型的值被复制
  • [drop] 丢弃

    • 允许此类型的值被弹出/丢弃
  • [store] 存储

    • 允许此类型的值存在于全局存储的某个结构体中
  • [key] 键值

    • 允许此类型作为全局存储中的键(具有 key 能力的类型才能保存到全局存储中)
  • 基础类型的abilities

    • boolu8u16u32u64u128u256, address 都具备: copydrop,  store 的能力
    • signer 只有 drop 的能力,也就是说signer的值不能拷贝和存储
    • vector 有copy和drop的能力,是否有store的能力要看是哪种T
    • 引用类型:&和&mut有copy和drop的能力,但没有store的能力
    • 所有的基础类型都不具备key的能力
  • Struct的能力声明,用‘has’

    • 例子:struct Pair has copy, drop, store { x: u64, y: u64 }

    • 如果一个Struct声明自己有copy,drop,store的能力,那么它的每一个field都必定具备此能力

    • 如果一个Struct声明自己有key的能力,它的每一个field一定具备store的能力,但不具备key的能力

全局存储

Aptos上采用的基于Move的存储结构和以太坊系所有的账本式记账方式的存储结构并不相同。在Aptos上,所有的资源都存储在账户(address)根节点下。每个根节点下可以存储资源resource和module代码,用伪代码表示可以是:

struct GlobalStorage {
  resources: Map<(address, ResourceType), ResourceValue>
  modules: Map<(address, ModuleName), ModuleBytecode>
}

全局存储的操作,有五种指令:

  • move_to<T>(&signer,T): 在 signer.address 下发布 T
  • move_from<T>(address):T :从 address 下删除 T 并返回T
  • borrow_global_mut<T>(address): &mut T :返回 address 下 T 的可变引用
  • borrow_global<T>(address): &T :返回 address 下 T 的不可变引用
  • exists<T>(address): bool :返回 address 下是否存在T

Move对空指针的防范

Move在静态引用安全性方面有两个很重要的保障措施, 即通过禁止返回全局引用,以及需要使用 acquires 标注函数来防止空引用。

  • (1)函数的返回值不能是一个指向全局存储的引用

    • 例子:当函数返回一个对全局存储的引用时,将无法编译通过
    struct R has key { f: u64 }
    // will not compile
    fun ret_direct_resource_ref_bad(a: address): &R {
        borrow_global<R>(a) // error!
    }
    // also will not compile
    fun ret_resource_field_ref_bad(a: address): &u64 {
        &borrow_global<R>(a).f // error!
    }
    
  • (2)要求函数使用acquires标注,当且仅当以下情况出现时,函数必须要用aquires标注

    • 函数体中包含:move_from<T>borrow_global_mut<T> 或 borrow_global<T> 指令
    • 函数体中包含其他被 acquires标注的函数

函数

函数定义

fun <identifier><[type_parameters: constraint],*>([identifier: type],*): <return_type> <acquires [identifier],*> <function_body>

举例:

fun foo<T1, T2>(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) }

函数可见性

  • Module中的函数,默认为private,只能在同module中被调用,不可被外部module和script调用

  • 如果函数要被外部module和script调用,需要被定义为public或public(friend)

  • public修饰的函数可以被:(1)同module调用,(2)外部module调用,(3)外部scripte调用

  • public(friend)修饰的函数可以被:(1)同module调用,(2)friend list中声明的module调用

    • 举例
    address 0x42 {
    module m {
        friend 0x42::n;  // friend declaration
        public(friend) fun foo(): u64 { 0 }
        fun calls_foo(): u64 { foo() } // valid
    }
    
    module n {
        fun calls_m_foo(): u64 {
            0x42::m::foo() // valid
        }
    }
    
    module other {
        fun calls_m_foo(): u64 {
            0x42::m::foo() // ERROR!
    //      ^^^^^^^^^^^^ 'foo' can only be called from a 'friend' of module '0x42::m'
        }
    }
    }
    
    script {
        fun calls_m_foo(): u64 {
            0x42::m::foo() // ERROR!
    //      ^^^^^^^^^^^^ 'foo' can only be called from a 'friend' of module '0x42::m'
        }
    }
    
    • friend用法:The friend syntax is used to declare modules that are trusted by the current module. A trusted module is allowed to call any function defined in the current module that have the public(friend) visibility.
  • entry修饰符

    • 旨在表明module的调用入口
    • 被entry修饰的public函数可以安全地直接被链外的script脚本调用
    • 可以修饰public函数,也可以修饰private函数
    • 被entry修饰的函数可以接收String、vector等基础类型的变量,但不能有struct类型的,并且不能有任何返回值。

函数的返回值

  • 函数体的最终计算结果,就是其返回值。当然,也可以用return显式声明
  • 例如:
    fun add(x: u64, y: u64): u64 {
    x + y
}

小结

快速学习完The Move Book后,对Move语言的基本理解,可以概括为以下几个方面:

  • Move是一种智能合约语言,目前有两个以高性能著称的L1链Aptos和SUI采用其作为智能合约语言。
  • Move语言以Module的形式发布到链上,Module中包含了函数和自定义的结构体Struct,结构体由Field组成,Field可以是各种基础类型,比如Address,Bool,u8,u64等。
  • 标记为entry的函数可以作为入口函数,public函数可以被外部的module或者脚本调用。函数和其他语言中的函数在使用方面没有太大的区别。
  • 如果一个结构体不能被copy或drop,就可以称作资源,资源必须在函数体结束前转让所有权,这个特性使得它特别适合用来代表token。因为结构体默认情况下是线性的、短暂的,不能复制,不能丢弃,不能存储在全局存储中,为了方便拓展,Move定义了四种能力drop,copy,store,key用于扩展操作。