Move 有两种不同类型的程序: 模块和脚本。模块是定义结构类型以及操作这些类型的函数的库。结构类型定义 Move 的全局存储模式,模块函数定义更新存储的规则。模块本身也存储在全局存储中。脚本是类似于传统语言中的主函数的可执行入口点。脚本通常调用已发布模块的函数对全局存储进行更新。脚本是不在全局存储中发布的临时代码段。
Move的源文件(或编译单元)可能包含多个模块和脚本。但是,发布模块或执行脚本是独立的 VM 操作。
一、脚本
脚本具有如下结构:
script {
<use>*
<constants>*
fun <identifier><[type parameters: constraint]*>([identifier: type]*) <function_body>
}
脚本块必须以它的所有 use 声明开始,然后是任何常量和(最后)主函数声明。Main 函数可以有任何名称(也就是说,它不需要被称为 main) ,是脚本块中唯一的函数,可以有任意数量的参数,并且不能有返回值。下面是每个组件的示例:
script {
// Import the Debug module published at the named account address Std.
use Std::Debug;
const ONE: u64 = 1;
fun main(x: u64) {
let sum = x + ONE;
Debug::print(&sum)
}
脚本的能力非常有限ーー它们不能声明好友(Friends)、结构类型或访问全局存储。它们的主要用途是调用模块函数。
二、 模块
模块具有以下语法:
module <address>::<identifier> {
(<use> | <friend> | <type> | <function> | <constant>)*
}
其中 < address > 是有效的命名地址或文字地址。例如:
module 0x42::Test {
struct Example has copy, drop { i: u64 }
use Std::Debug;
friend 0x42::AnotherTest;
const ONE: u64 = 1;
public fun print(x: u64) {
let sum = x + ONE;
let example = Example { i: sum };
Debug::print(&sum)
}
}
"module 0x42::Test"部分表示模块 Test 将在全局存储中的账户地址0x42下发布。模块也可以使用命名地址声明。例如:
module TestAddr::Test {
struct Example has copy, drop { a: address}
use Std::Debug;
friend TestAddr::AnotherTest;
public fun print() {
let example = Example { a: @TestAddr};
Debug::print(&example)
}
}
因为命名地址只存在于源语言级别和编译期间,所以在字节码级别,命名地址将完全被它们所代表的值所取代。例如,如果我们有以下代码:
script {
fun example() {
MyAddr::M::foo(@MyAddr);
}
}
然后我们将 MyAddr 设置为0xC0FFEE,那么它将等效于下面的操作:
script {
fun example() {
0xC0FFEE::M::foo(@0xC0FFEE);
}
}
但是在源代码级别,这两个函数是不等价的ーー函数 M: : foo 必须通过 MyAddr 命名的地址来访问,而不是通过分配给该地址的数值来访问。模块名称可以以字母 a 到 z 或字母 A 到 Z 开头。在第一个字符之后,模块名称可以包含下划线 _、字母 a 到 z、字母 A 到 Z 或数字0到9。
module my_module {}
module FooBar42 {}
通常,模块名称以大写字母开头。一个名为 MyModule 的模块应该存储在一个名为 MyModule.move 的源文件中。
模块块内的所有元素可以以任意顺序出现。基本上,模块是类型和函数的集合。Uses关键字可以声明来自其他模块的导入类型,使用Friends可以指定受信任模块的列表,Constants可以定义该模块函数中使用的私有常量。