sui move笔记
前言
一些疑问:
- sui 和move是什么关系?
基础
基本数据类型
Move 的基本数据类型包括: 整型 (u8, u32,u64, u128,u258)、布尔型 boolean 和地址 address。
Move 不支持字符串和浮点数。
_u8:代表8位无符号整数类型,范围是0~255。占用内存8位
_u16:代表16位无符号整数类型,范围是0~65535。占用内存16位
_u32:代表32位无符号整数类型,范围是0~4294967295。占用内存32位
_u64:代表64位无符号整数类型,范围是0~18446744073709551615。占用内64位
以此类推
整型
module ch02::int {
fun main() {
// define empty variable, set value later
let a: u8;
a = 10;
let a = 1u32;
// define variable, set type
let a: u64 = 10;
// finally simple assignment
let a = 10;
// simple assignment with defined value type
let a = 10u64;
// in function calls or expressions you can use ints as constant values
if (a < 10) {};
// or like this, with type
if (a < 10u64) {}; // usually you don't need to specify type
let b = 1u256;
// or like this, with type
if (b < 10u256) {}; // usually you don't need to specify type
}
}
let a = 10 默认不手动标记类型的整型是 u64 类型,也就是等同于 let a:u64 = 10 或者 let a = 10u64
布尔型
布尔类型就像编程语言那样,包含false和true两个值。
module book::boolean {
fun main() {
// these are all the ways to do it
let b : bool; b = true;
let b : bool = true;
let b = true;
let b = false; // here's an example with false
}
}
地址
地址是区块链中交易发送者的标识符,转账和导入模块这些基本操作都离不开地址。
module book::addr {
fun main() {
let addr: address; // type identifier
addr = @ch02;
}
}
模块
模块是发布在特定地址下的打包在一起的一组函数和结构体。
模块以module关键字开头,后面跟随地址::模块名称和大括号,大括号中放置模块内容。
module book::math {
public fun sum(a: u64, b: u64): u64 {
a + b
}
}
注意:
- 模块在发布者的地址下发布。标准库在 0x1 地址下发布。
- 发布模块时,不会执行任何函数。要使用模块就得使用脚本。
- 模块名推荐使用小写
- 模块是发布代码供他人访问的唯一方法。新的类型和 Resource 也只能在模块中定义。默认情况下,模块将在发布者的地址下进行编译和发布
导入
Move 在默认上下文中只能使用基本类型,也就是整型、布尔型和地址,可以执行的有意义或有用的操作也就是操作这些基本类型,或者基于基本类型定义新的类型。
除此之外还可以导入已发布的模块(或标准库)。
- 直接导入
module book::m {
fun main(a: u8) {
std::debug::print(&a);
}
}
在此示例中,我们从地址0x1(标准库)导入了 debug 模块,并使用了它的 print 方法
use关键字
要使代码更简洁(注意,0x1 是特殊的地址,实际地址是很长的),可以使用关键字use:
use <address>::<ModuleName>;
这里 </address/> 是模块发布object的地址, 是模块的名字。非常简单,例如,我们可以像下面这样从 0x1 地址导入 vector 模块。
use 0x1::vector;
- 访问模块的内容
要访问导入的模块的方法(或类型),需要使用::符号。非常简单,模块中定义的所有公开成员都可以通过双冒号进行访问。
module book::m_use {
use std::debug::print;
fun main(a: u8) {
print(&a);
}
}
在模块中导入
在模块中导入模块必须在 module {} 块内进行:
module book::math {
use std::vector;
// you are free to import any number of modules
public fun empty_vec(): vector<u64> {
let v = vector::empty<u64>();
v
}
}
成员导入
导入语句还可以进一步被扩展,可以直接导入模块的成员:
module book::m_use2 {
// single member import
use sui::tx_context::TxContext;
use sui::tx_context::sender;
// multi member import (mind braces)
use std::vector::{
empty,
push_back
};
fun main(ctx: &mut TxContext) {
// use functions without module access
let vec = empty<u8>();
push_back(&mut vec, 10);
let _ = sender(ctx);
}
}
使用 Self 来同时导入模块和模块成员
导入语句还可以进一步扩展,通过使用 Self 来同时导入模块和模块成员,这里 Self 代表模块自己。
module book::m_self {
use 0x1::vector::{
Self, // Self == Imported module
empty
};
fun main() {
// `empty` imported as `empty`
let vec = empty<u8>();
// Self means vector
vector::push_back(&mut vec, 10);
}
}
使用 use as
当两个或多个模块具有相同的名称时,可以使用关键字as更改导入的模块的名称,这样可以在解决命名冲突的同时缩短代码长度。
语法:
use <address>::<ModuleName> as <Alias>;
module ch04::m_as1 {
use 0x1::vector::{
Self as v,
empty as empty_vec
};
fun main() {
// `empty` imported as `empty_vec`
let vec = empty_vec<u8>();
// Self as V = vector
v::push_back(&mut vec, 10);
}
}
函数
Move 中代码的执行是通过调用函数实现的。函数以 fun 关键字开头,后跟函数名称、扩在括号中的参数,以及扩在花括号中的函数体。
module book::f01 {
fun function_name(arg1: u64, arg2: bool): u64 {
// function body
10
}
}
- 注意:Move 函数使用snake_case命名规则,也就是小写字母以及下划线作为单词分隔符。
返回值:
module book::math {
fun zero(): u8 {
0
}
}
第一步:我们定义一个 math 模块,它有一个函数:zero(),该函数返回 u8 类型的值 0。0 之后没有分号,因为它是函数的返回值
return关键字:
module book::m {
public fun conditional_return(a: u8): bool {
if (a == 10) {
return true // semi is not put!
};
if (a < 10) {
true
} else {
false
}
}
}
多个返回值及解构:
要指定多个返回值,需要使用括号:
module book::math {
// ...
public fun max(a: u8, b: u8): (u8, bool) {
if (a > b) {
(a, false)
} else if (a < b) {
(b, false)
} else {
(a, true)
}
}
}
在另一个模块中使用该函数的返回值。
module book::math_use {
use book::math::sum;
use book::math::max;
fun use_max(){
let (a,b)= max(1u8,2u8);
}
}
上面例子中,我们解构了一个二元组,用函数 max 的返回值创建了两个新变量。
公有、私有方法、friend方法、native本地方法:
默认情况下,模块中定义的每个函数都是私有的,无法在其它模块或脚本中访问。可能你已经注意到了,我们在 Math 模块中定义的某些函数前有关键字 public:
- 关键字 public 将更改函数的默认可见性并使其公开,即可以从外部访问。
- 默认情况下函数是私有函数只能在定义它们的模块中访问。
- 私有函数只能在定义它们的模块中访问。
module book::math {
public fun sum(a: u64, b: u64): u64 {
a + b
}
fun zero(): u8 {
0
}
}
friend 方法:
friend 方法可以指定指定的模板能调用,目前只能在同一个包内生效
module book::friends {
friend book::m;
public(friend) fun a_less_10(a: u8): bool {
if(a < 10u) return true;
false
}
}
本地方法:
有一种特殊的函数叫做"本地方法"。本地方法实现的功能超出了 Move 的能力,它可以提供了额外的功能。本地方法由 VM 本身定义,并且在不同的VM实现中可能会有所不同。这意味着它们没有用 Move 语法实现,没有函数体,直接以分号结尾。关键字 native 用于标记本地函数,它和函数可见性修饰符不冲突,native 和 public 可以同时使用。
module book::m {
native public fun borrow_address(s: &signer): &address;
// ... some other functions ...
}
运算符
as
as 在move 中有两个用法:
1.给包取别名
module book::m_as1 {
use 0x1::vector::{
Self as v,
empty as empty_vec
};
fun main() {
// `empty` imported as `empty_vec`
let vec = empty_vec<u8>();
// Self as V = vector
v::push_back(&mut vec, 10);
}
}
2.整型类型转换 语法 (整型A as 整型 B) 当需要比较值的大小或者当函数需要输入不同大小的整型参数时,你可以使用as运算符将一种整型转换成另外一种整型 注意就是括号是一定不能省的
module book::op_as {
fun main(){
let _a:u64 = (10u8 as u64);
let _b:u8 = (a as u8);
}
}
+ - * /
注意:
- 负数做减法一定要检查是否产生负数
- 做加法乘法注意 溢出报错
- 除法小心精度丢失问题
- 得益于Move的安全设计,溢出和负数不会让合约产生安全问题,因为程序会终止运行,但是程序终止会给用户带来不好的体验,代码不是很好调试,建议还是做好溢出边界判断
module book::op_arith {
fun main(){
let _add_op = 1 + 1;
let _mut_op = 1*1;
let _minu_op = 100 -1;
let _div_op = 100/1;
}
}
常量
Move 支持模块级常量。常量一旦定义,就无法更改,所以可以使用常量为特定模块或脚本定义一些不变量,例如角色、标识符等。
常量可以定义为基本类型(比如整数,布尔值和地址),也可以定义为数组。我们可以通过名称访问常量,但是要注意,常量对于定义它们的模块来说是本地可见的。
module book::consts {
use std::debug;
const RECEIVER: address = 0x999;
const ErrO1: u64 = 1000
fun main(account: &signer) {
debug::print<address>(&RECEIVER);
let _ = RECEIVER;
let _ = ErrO1;
}
}
注意:
- 一旦定义,常量是不可更改的。
- 常量在模块是本地可见的,不能在外部使用。
- 可以将常量定义为一个表达式(带有花括号),但是此表达式的语法非常有限。