Solidity 与 Move 函数深度对比,你不知道的都在这里了
2022-09-1713:55
MoveFuns
2022-09-17 13:55
MoveFuns
2022-09-17 13:55
收藏文章
订阅专栏

最近比较忙,一直有人在催稿,但是一直没时间更新专栏,抓住中秋假期的尾巴,写篇 Move 教程。

在我们的 Move 教程基础篇中,前面讲了如何搭建 Move 环境、Move 的基本数据类型,今天我们要讲一讲 Move 的函数定义以及函数调用。对于所有的高级语言,函数定义其实都差不多,无非是一些细节上的不同。因为 Move 的智能合约语言,所以对比 Solidity 来讲(就事论事,没有喷 Solidity 的意思)。

Move 与 Solidity 的函数对比

首先需要了解一点,Move 的代码跟状态是分离的。对比 Solidity,Move 的函数显得非常的干净:

  • 没有 Library 的概念

  • 没有继承的概念

  • 没有特殊函数(Solidity 有 fallback、receive 等函数,都是为了处理特殊的状态)

  • 没有 Solidity 的 calldata、memory、storage 等变量(但是有 mutable 和 immutable 的引用变量)

  • 没有 Modifier

  • 没有 payable

在函数定义方面,对比 Solidity,除了语法上的区别,还有几个重要的不同:

  • 函数可见性(Move 有更丰富的函数可见性)

  • Move 可以传值,也可以传引用(其他高级语言也是这样)

  • 返回结构体 struct(这个在 Move 进阶课程中再介绍)和 tuple 元组(也就是多个值),还可以返回引用

在函数调用方面,Move 和 Solidity 也有很大的不同:

  • Move 没有 Solidity 的 msg 上下文了(比较难理解的特性,容易出现安全问题)

  • Move 是静态调用(没法构造递归调用,也没有 Solidity 合约 DelegateCall 这些容易带来安全隐患的东西)

  • Move 只能修改当前合约的数据,Solidity 被调用合约可以修改调用合约的数据(反常识)

Move 是一门更“正常”的编程语言,除了 acquires 关键字之外,其他的都跟我们对高级语言的理解是一样的。Solidity 因为代码和状态没有完全分离,所以添加了很多“为了解决某个状态问题而专门设计的特性”,例如 payable 和 fallback 函数等等,还有容易让人混淆的 msg 上下文等概念,有点缝缝补补的感觉。(这里暂时不涉及泛型对函数定义和调用的影响,在后面讲泛型的时候,专门介绍)。

Move 的函数签名

前面对比了 Move 跟 Solidity 的不同,接下来我们介绍一下 Move 的函数签名。

高级语言的函数签名都大同小异,一般包含以下部分:

  • 函数可见性

  • 函数名

  • 函数参数 ( 包括传值和传引用 )

  • 函数返回值

  • acquires(这是智能合约特有的关键字)

Move 的编程风格有点类似于 Rust,以下是函数的定义:

函数可见性 fun 函数名 ( 参数列表 ): 返回值列表 acquires 结构体列表 {
函数体
return 结果
}

这里有几个地方简单说明一下:

  • fun是函数定义的关键字(Rust 是 function,为了区分 Move 跟 Rust,Move 团队特意引入了一些小的区别)

  • Move 函数使用 snake_case 命名规则,小写字母以及下划线作为单词分隔符

  • :指定返回值,可以是列表,列表用()括起来,也支持 struct 类型的返回值(跟 Solidity 有区别),不指定表示没有返回值

  • acquires指定函数引用到的 struct(这块在后面的进阶教程中,介绍 Struct 的时候再讲),不指定表示没有引用

  • return 在函数体的最后可以省略,同时,不需要;,否则编译会抛错

函数可见性

前面在讲函数签名的时候没有讲 Move 函数的可见性。Move 的可见性非常有意思,值得单独拿一个小节来讲。

了解 Solidity 的人都知道,Solidity 只有publicprivate两种可见性。对于开源的 DeFi 系统来说,函数要么是所有人可见,要么说有人都不可见,有点激进。不知道大家用过 Java 没有?Java 有 4 种级别的函数可见性:publicfriendly( 无关字 )protectedprivate,其中,protected表示继承关系的可见性,friendly指定 package 级别的可见性。Move 也设计了 4 种函数的可见性,分别是(关于什么是模块 Module,暂时可以类比 Solidity 的 Contract,在进阶课程会专门介绍):

  • private:当前 Module 可见(Move 函数的默认可见性)

  • friend:当前 address 下被授权的 Module 可见(可以理解为 address 级别的可见性,类比 Java 的 package 级别的可行性。Solidity 通常使用 Modifier 来控制调用权限,这依赖开发者的个人能力,容易引发安全隐患,被黑客绕过)

  • public:所有 Module 可见,但是用户不能直接作为交易使用(跟 Solidity 不一样,Solidity 是调用 public 的合约函数发起链上交易)

  • entry 或者 script:交易的入口(可以理解为可编程的交易,在讲 Module 的时候再详细介绍)

以上 4 种可见性,可见范围逐渐放大。

1. public & private

我们先看看publicprivate的使用。publicprivate跟 Solidity 大同小异,没什么特别的地方。下面是在 public 函数中调用了一个 private 函数:

这里简单说明一下:

  • ① 是一个 private 的函数,③ 是一个 public 的函数,尤其要注意的是,Move 默认是函数可见性是 private 的(这点跟 Solidity 不一样,Solidity 默认是 public 的),能很好的避免函数的泄露

  • ② 是返回值,需要注意,这里不能有;,否则编译不通过

  • ④ 和 ⑤ 都是对当前合约的test_private函数的调用,Self 表示当前 Module

2. friend

接下来,我们再介绍一下friend可见性的使用。

可见性的本质是控制函数对外的访问权限。friend的作用是控制「相同 address 下,跨 Module 的函数调用」,这样精细化的访问控制,能更好的避免函数泄露的问题。下来是friend使用的例子。

我们定义了一个 function1 的模块,简单说明一下:

  • ② 通过public(friend)关键字定义了一个friend可见性的 test_friend1 的函数

  • ① 通过friend授权让 std 地址(本实例是 0x2 地址)的 function2 的模块可以调用

我们再定义一个 function2 的模块,对 test_friend1 进行调用:

简单说明一下(这块跟调用 public 函数一样,没有区别,注意一下跨 Module 调用以及 use 关键字):

  • ③ 是引入 function1 的模块

  • ④ 调用 function1 的 test_friend1 函数

以上是friend可见性函数的调用例,都是在 std 的 address 下(本例是 0x2)。我们来看一下,friend的函数能不能授权给其他的 address 调用呢?

上图是假设授权给 0x3 这个地址的 function3 的模块使用,可以看到编译错误,所以friend可见性的函数是不可以跨地址使用的。

3. entry & script

entry 或者 script 可见性,也是 Move 很有意思的一个函数可见性,是用户跟链上交互的入口。

在 EVM 合约开发中,Solidity 代码编写完之后,要将所有的代码部署到链上,然后,用户通过调用 Contract 的 public 方法发起交易。这么处理当然没问题,但是灵活性不足,因为 Contract 是部署在链上,并且部署之后不能修改(或者说修改很麻烦),而用户的需求往往会随着事情的发展而改变的。

Move 在合约设计上,将链上和链下区分开。也就是说,Move 的交易是链下的,是可以编程的,拥有极好的灵活性,并不像 Solidity 那样通过函数名来发起交易。而 entry 或者 script 可见性正是用来代表交易的函数。


Move 最早叫 script 可见性,后面修改成 entry 可见性了,更直接,更好理解,也能跟模块化的 script 进行区分(讲模块化的时候会讲到)。不管叫什么,也不管关键字是什么,理解了它的应用场景最重要,使用起来很简单。下面是例子:

简单说明一下:

  • ① 是使用public(script)定义的函数

  • ② 使用entry关键字定义函数

  • ③ 编译的时候提示了public(script)将会弃用

传值 & 传引用

前面对 Move 的 4 种函数可见性做了详细介绍。跟 Solidity 对比,除了上面这些区别,还有一个很大的区别,Move 的参数既可以传值,也可以传引用(常见的高级语言也是这样,例如 Java),并且区分mutable ref「可变引用」和immutable ref「不可变引用」,这是 Rust 的风格。

我们来理解一下immutable refmutable ref

  • immutable ref可以类比只读锁,mutable ref可以类比独占的写锁。

  • 同一个结构体的实例,同时可以有多个rimmutable ef

  • 同一个结构体的实例,同时只能有一个mutable ref

  • 同一个结构体的实例,mutable refimmutable ref不能同时存在,但是mutable ref可以当immutable ref使用

所以我们在定义函数的时候一定要注意,引用参数应该是「可变」还是「不可变」。「可变」意味着函数逻辑可以修改当前结构体 instance 的数据,而「不可变」意味着只读。下面是例子:

简单说明一下:

  • ① 定义了一个 strut(这个在进阶课程会详细介绍)

  • 对 immutable ref 的定义是&,而 mutable ref 的定义是&mut

  • ② 定义了一个 immutable ref 参数的函数,只读

  • ③ 定义了一个 mutable ref 参数的函数,可以修改

返回 Struct & 引用 & 元组

对比 Solidity,Move 函数的返回值也很有特点。

Solidity 的函数最早只能返回基本数据类型,后面增加了返回结构体的支持。但是,Move 函数的返回值更加自由,能返回任何形式的数据,例如immutable refmutable ref、tuple 元组。

上面是 Move 函数返回各种类型的数据的例子,简单说明一下:

  • ① 定义了一个 strut

  • ② 函数返回了一个 struct

  • ③ 函数返回了一个 immutable 的引用,以为着数据只读

  • ④ 函数返回了一个 immutable 的引用,意味着数据可以被修改

  • ⑤ 函数返回了一个 tuple 类型,调用了 ② 的函数

跟我学 Move & 完整代码

当前教程详细介绍了 Move 函数如何定义和调用,重点对比 Solidity、介绍了 Move 函数的可见性、如何传引用、如何返回数据类型。完整的代码如下(Module 和 Struct、acquires 以及函数的泛型在后面的进阶课程再介绍):

module std::function1 {

fun test_private() :bool {
true
}

public fun test_public() : bool {
Self::test_private();
test_private();
false
}

friend std::function2;
// friend 0x3::function3;

public(friend) fun test_friend1() : u64 {
1
}

public(script) fun test_script() {}

public entry fun test_entry() {}

struct Test{
flag:bool
}

fun test_immutable(test: &Test):bool {
return test.flag
}

fun test_mutable(test:&mut Test) {
test.flag = true;
}

fun test_struct_return() : Test {
Test{
flag: true
}
}

fun test_im_return(test: &Test) : &Test {
test
}

fun test_mut_return(test: &mut Test) : &mut Test {
test
}

fun test_tuple_return() : (Test, bool) {
(test_struct_return(),false)
}
}

对比 Solidity 的函数,Move 的函数确实显得更简洁、丰富、自由。

我是@MoveContract(twittter),也可以关注@Move 小王子 的知乎专栏“手把手写 Move 智能合约”,跟我一起学习 Move。下期是 Move 基础课程的最后一讲,主要讲 Move 经常用的到一些类库,例如 vector、table 等等。

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

专栏文章
查看更多
数据请求中

推荐专栏

数据请求中

一起「遇见」未来

DOWNLOAD FORESIGHT NEWS APP

Download QR Code