Skip to main content
欢迎来到”准备食材”阶段!在第一章中,你搭建了厨房,制作了第一道菜品。现在,让我们深入了解各种”食材”——Move语言的基础数据类型和语法结构。 这一章的目标是让你掌握Move的基础语法。我们不会涉及复杂的链上交互,重点在于”如何写出合法的Move代码”。就像学习烹饪需要先认识各种食材一样,熟悉这些基础是后续学习的基石。

2.1 基本数据类型 (Primitives)

无符号整型家族:u8/u64/u128/u256

Move提供了四种无符号整数类型,用于表示正整数: 类型范围:
  • u8: 0 到 255 (2^8 - 1)
  • u64: 0 到 18,446,744,073,709,551,615 (2^64 - 1)
  • u128: 0 到 2^128 - 1 (约3.4e38)
  • u256: 0 到 2^256 - 1 (约1.16e77)
选择策略:
  • u8:适用于小的计数器、状态标志(如年龄、选项枚举)
  • u64:最常用!适用于代币余额、时间戳、大多数业务逻辑
  • u128:大额金融计算、科学计算
  • u256:加密计算、需要极大数值的特殊场景
// 整型使用示例
fun integer_example() {
    let small: u8 = 255;                // 最大u8值
    let balance: u64 = 1000000000;      // 10亿,适合代币余额
    let big_number: u128 = 100000000000000000000; // 10^20
    let huge: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935; // 2^256-1
}
溢出处理: Move默认进行溢出检查。如果运算超出范围,交易会中止(abort)。
fun overflow_demo() {
    let x: u8 = 255;
    // 以下代码会导致交易中止
    // let y = x + 1; // 256超出u8范围
    
    // 安全的方式
    if (x < 255) {
        let y = x + 1;
    };
}
🧑‍🍳 厨师提示:在Aptos Move 2.0+中,你可以使用标准库的安全数学函数来处理可能溢出的运算。

有符号整型家族(Move 2.3新增):i8/i16/i32/i64/i128/i256

从Move 2.3开始,Move引入了有符号整数类型,用于表示正负数。 类型范围(以i8为例):
  • i8: -128 到 127
  • i16: -32,768 到 32,767
  • i32: -2,147,483,648 到 2,147,483,647
  • i64: -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
  • i128: -2^127 到 2^127-1
  • i256: -2^255 到 2^255-1
使用场景:
fun signed_example() {
    let temperature: i8 = -10;          // 温度可以是负数
    let profit: i64 = -5000;            // 利润可以是负的(亏损)
    let score_difference: i32 = 15;     // 分数差可以是正负
    
    // 注意:资产余额通常还是用无符号整数
    // let balance: i64 = -100; // 通常不这样用!
}

布尔值:bool

布尔类型只有两个值:truefalse,用于逻辑判断。
fun bool_example() {
    let is_active: bool = true;
    let is_completed: bool = false;
    
    // 逻辑运算
    let and_result: bool = is_active && is_completed;  // false
    let or_result: bool = is_active || is_completed;   // true
    let not_result: bool = !is_active;                 // false
    
    // 在条件语句中使用
    if (is_active) {
        // 执行某些操作
    };
}

地址:address

地址类型表示Aptos区块链上的账户地址。地址是256位(32字节)的值,通常以十六进制表示。
fun address_example() {
    // 字面量地址(注意@符号)
    let std_lib_addr: address = @0x1;      // 标准库地址
    let my_address: address = @0x1234;     // 自定义地址
    
    // 地址没有算术运算
    // let wrong = my_address + 1; // 编译错误!
    
    // 但可以比较
    let is_equal: bool = std_lib_addr == @0x1;  // true
}
重要地址:
  • @0x1:Aptos标准库地址
  • @0x2:Aptos框架地址
  • @0x3:Aptos区块元数据地址
  • @0x4:Aptos字符串操作地址

关键类型signer详解

signer是Move中一个特殊且重要的类型,它代表交易的发送者(签名者)。 signeraddress的核心区别:
特性signeraddress
本质权限的证明位置的标识
获取方式只能作为入口函数参数传入可以直接使用或计算
能做什么授权操作、支付Gas、代表用户行动仅作为目标标识
可变性不可变引用(&signer)普通值
// signer 使用示例
module my_addr::auth_example {
    // 入口函数接收signer参数
    public entry fun do_something(account: &signer) {
        // account是当前交易的签名者
        // 可以:
        // 1. 从该账户提取资源
        // 2. 以该账户身份授权操作
        // 3. 支付Gas费
        
        let addr: address = signer::address_of(account);
        // 现在我们可以使用这个地址
    }
    
    // 错误示例
    fun wrong_example() {
        // 不能直接创建signer!
        // let s: signer = ...; // 编译错误
        
        // 不能从address转换到signer
        // let addr: address = @0x1234;
        // let s: signer = addr as signer; // 编译错误
    }
}
核心理解signer是”谁在执行操作”的证明。当函数需要&signer参数时,意味着这个函数需要调用者的明确授权。

动态数组:vector

vector是Move中唯一的集合类型,用于存储同类型元素的动态数组。 基础操作:
fun vector_basics() {
    // 1. 创建空vector
    let empty: vector<u64> = vector[];
    
    // 2. 创建带初始值的vector
    let numbers: vector<u64> = vector[1, 2, 3, 4, 5];
    
    // 3. 添加元素(需要可变引用)
    vector::push_back(&mut empty, 10);
    vector::push_back(&mut empty, 20);
    
    // 4. 获取长度
    let len: u64 = vector::length(&numbers);  // 5
    
    // 5. 通过索引访问(返回引用)
    let second: &u64 = vector::borrow(&numbers, 1);  // 2(索引从0开始)
    
    // 6. 修改元素
    let mut nums = vector[1, 2, 3];
    *vector::borrow_mut(&mut nums, 0) = 100;  // 修改第一个元素为100
    
    // 7. 删除并返回最后一个元素
    let last: u64 = vector::pop_back(&mut nums);
}
高级操作:
fun vector_advanced() {
    let v = vector[3, 1, 4, 1, 5, 9];
    
    // 检查是否为空
    let is_empty: bool = vector::is_empty(&v);  // false
    
    // 判断是否包含某个元素
    let contains: bool = vector::contains(&v, &4);  // true
    
    // 查找元素索引
    let index_opt: option<u64> = vector::index_of(&v, &5);
    
    // 删除指定索引的元素(消耗较大)
    let removed = vector::remove(&mut v, 2);  // 删除索引2的元素(值4)
    
    // 交换两个元素
    vector::swap(&mut v, 0, 1);
}
🧑‍🍳 重要提示vector索引从0开始,访问越界会导致交易中止。总是检查索引是否有效!

🍳 厨师提示:Move中为什么没有字符串类型?

这是一个常见问题。Move没有内置的string类型,原因如下:
  1. 性能考虑:字符串操作(拼接、分割、编码转换)在区块链上成本高昂
  2. 确定性需求:智能合约需要确定性的执行结果,字符串处理的复杂性可能导致不确定性
  3. 实际用途:大多数合约场景只需要短标识符(名称、符号、URI)
解决方案:
  • 短文本:使用vector<u8>,如b"APT"(字节字符串字面量)
  • 标识符:通常足够用vector<u8>表示
  • 长文本/富文本:存储哈希值在链上,完整内容存储在链下(如IPFS、Arweave)
// 字符串表示示例
fun string_representation() {
    // 字节字符串字面量(方便写法)
    let name: vector<u8> = b"Alice";
    let symbol: vector<u8> = b"APT";
    let url: vector<u8> = b"https://aptoslabs.com";
    
    // 手动创建vector<u8>
    let hello: vector<u8> = vector[
        72, 101, 108, 108, 111  // ASCII: H, e, l, l, o
    ];
}

2.2 流程控制 (Flow Control)

条件分支:if/else if/else

Move的条件语句与其他语言类似,但有一些独特之处:
fun condition_example(score: u64) {
    let grade: vector<u8>;
    
    if (score >= 90) {
        grade = b"A";
    } else if (score >= 80) {
        grade = b"B";
    } else if (score >= 70) {
        grade = b"C";
    } else {
        grade = b"F";
    };
    
    // 注意:if语句以分号结尾!
}
if表达式(返回值的if): 在Move中,if可以返回值,但所有分支必须返回相同类型。
fun if_expression_example(age: u8) {
    // if作为表达式使用(类似三元运算符)
    let status: vector<u8> = if (age >= 18) {
        b"Adult"
    } else {
        b"Minor"
    };
    
    // 更复杂的例子
    let discount: u64 = if (age < 12) {
        50  // 儿童5折
    } else if (age >= 65) {
        30  // 老人7折  
    } else {
        0   // 全价
    };
}
重要规则if表达式必须用分号结束,除非它是函数体的最后一条语句。

循环结构:whileloop

while循环:在条件为真时重复执行
fun while_example() {
    let mut counter: u64 = 0;
    
    // 标准while循环
    while (counter < 5) {
        counter = counter + 1;
        // 注意:Move没有++或+=运算符
    };
    
    // 结果:counter = 5
}
loop循环:无限循环,必须配合break使用
fun loop_example() {
    let mut counter: u64 = 0;
    
    loop {
        counter = counter + 1;
        if (counter >= 5) {
            break  // 退出循环
        };
        // continue可以跳过剩余代码进入下一次迭代
    };
}

控制流语句:breakcontinuereturnabort

  • break:立即退出当前循环
  • continue:跳过当前循环剩余部分,进入下一次迭代
  • return:从函数中返回(可带返回值)
  • abort:中止整个交易,可带错误码
fun control_flow_demo() {
    let mut i: u64 = 0;
    
    // 使用break和continue
    while (true) {
        i = i + 1;
        
        if (i == 3) {
            continue; // 跳过i=3的情况
        };
        
        if (i == 5) {
            break; // 退出循环
        };
        
        // 当i=3时,这里不会执行
    };
    
    // 使用abort进行错误处理
    if (i != 5) {
        abort 1001; // 中止交易,错误码1001
    };
    
    // return提前返回
    if (i == 0) {
        return (); // 返回空元组
    };
    
    // 函数隐式返回最后表达式的值
    i * 2
}

最佳实践:避免无限循环的几种模式

在区块链环境中,无限循环是危险的(可能消耗所有Gas或导致交易失败)。以下是安全模式: 模式1:最大迭代次数限制
fun safe_loop_1() {
    let max_iterations: u64 = 1000;
    let mut i: u64 = 0;
    
    while (i < max_iterations) {
        // 处理逻辑
        i = i + 1;
    };
}
模式2:基于数据结构的循环
fun safe_loop_2(items: vector<u64>) {
    let mut i: u64 = 0;
    let len = vector::length(&items);
    
    while (i < len) {
        let item = vector::borrow(&items, i);
        // 处理item
        i = i + 1;
    };
}
模式3:超时机制
fun safe_loop_3(start_time: u64, timeout_seconds: u64) {
    loop {
        let current_time = aptos_framework::timestamp::now_seconds();
        
        // 检查是否超时
        if (current_time > start_time + timeout_seconds) {
            break
        };
        
        // 执行一些工作
        // ...
        
        // 短暂等待(在区块链中通常通过交易间隔实现)
        // 注意:在Move中不能直接"sleep"
    };
}
模式4:状态检查循环
fun safe_loop_4(mut remaining_work: u64) {
    while (remaining_work > 0) {
        // 每次迭代处理一部分工作
        let work_to_do = if (remaining_work > 100) { 100 } else { remaining_work };
        
        // 处理work_to_do个单位的任务
        // ...
        
        remaining_work = remaining_work - work_to_do;
    };
}

2.3 模块与函数 (Modules & Functions)

模块定义:module语法与命名规则

模块是Move代码的组织单元,类似于其他语言中的包或命名空间。 基本语法:
module <address>::<module_name> {
    // 模块内容:函数、结构体、常量等
}
示例:
// 文件:sources/my_math.move
module 0x1234::my_math {
    // 模块内容
}
使用命名地址(推荐): 在Move.toml中定义:
[addresses]
my_addr = "0x1234"
在代码中使用:
module my_addr::my_math {
    // 模块内容
}
命名规则:
  1. 模块名应该小写,使用下划线分隔单词(snake_case)
  2. 模块名在同一个地址下必须唯一
  3. 建议使用有意义的名称,反映模块功能

可见性修饰符:publicpublic(friend)

Move 2.0中的可见性系统:
  1. 私有(默认):仅在模块内可见
  2. public:任何模块都可调用
  3. public(friend):仅友元模块可调用
module my_addr::visibility_demo {
    // 1. 私有函数(默认)
    fun private_func(): u64 {
        42
    }
    
    // 2. 公共函数 - 任何模块都可以调用
    public fun public_func(): u64 {
        private_func() // 可以调用私有函数
    }
    
    // 3. 友元函数 - 只有友元模块可以调用
    public(friend) fun friend_func(): u64 {
        100
    }
    
    // 声明友元模块
    friend my_addr::another_module;
}

// 另一个模块
module my_addr::another_module {
    use my_addr::visibility_demo;
    
    fun test() {
        // 可以调用友元函数
        let x = visibility_demo::friend_func(); // 允许
        
        // 也可以调用公共函数
        let y = visibility_demo::public_func(); // 允许
        
        // 不能调用私有函数
        // let z = visibility_demo::private_func(); // 编译错误
    }
}

// 第三个模块(不是友元)
module my_addr::third_module {
    use my_addr::visibility_demo;
    
    fun test() {
        // 只能调用公共函数
        let y = visibility_demo::public_func(); // 允许
        
        // 不能调用友元函数
        // let x = visibility_demo::friend_func(); // 编译错误
    }
}

函数定义:参数、返回值、可见性

基本语法:
[可见性] fun 函数名(参数名: 参数类型, ...): 返回值类型 {
    // 函数体
    返回值表达式 // 不需要分号
}
示例:
module my_addr::function_demo {
    // 1. 无参数,无返回值
    fun simple_func() {
        // 做一些事情
    }
    
    // 2. 带参数,有返回值
    public fun add(a: u64, b: u64): u64 {
        a + b  // 最后一行表达式的值就是返回值
    }
    
    // 3. 提前返回
    public fun early_return(x: u64): u64 {
        if (x == 0) {
            return 0;  // 提前返回
        };
        
        x * 2  // 正常返回
    }
    
    // 4. 多个返回值(使用元组)
    public fun swap(a: u64, b: u64): (u64, u64) {
        (b, a)  // 返回元组
    }
    
    // 5. 没有显式返回值的函数实际上返回空元组 `()`
    public fun do_nothing(): () {
        // 隐式返回 ()
    }
}
元组解构:
module my_addr::tuple_demo {
    public fun get_min_max(numbers: vector<u64>): (u64, u64) {
        // 假设numbers不为空
        let mut min = *vector::borrow(&numbers, 0);
        let mut max = min;
        
        let mut i = 1;
        let len = vector::length(&numbers);
        while (i < len) {
            let num = *vector::borrow(&numbers, i);
            if (num < min) {
                min = num;
            };
            if (num > max) {
                max = num;
            };
            i = i + 1;
        };
        
        (min, max)
    }
    
    public fun use_tuple() {
        let numbers = vector[3, 1, 4, 1, 5, 9];
        let (min_val, max_val) = get_min_max(numbers);  // 元组解构
        // min_val = 1, max_val = 9
    }
}

重点:入口函数 entry 详解

入口函数是可以直接从外部交易调用的函数。这是Move 2.0的一个重要变化。 Move 2.0的变化:
  • Move 1.0:使用public(script)函数
  • Move 2.0:使用public函数,可标记为entry(可选但推荐)
module my_addr::entry_demo {
    // 入口函数 - 可以直接通过交易调用
    public entry fun transfer(
        sender: &signer,
        recipient: address,
        amount: u64
    ) {
        // 转账逻辑
        // 注意:entry函数不能有返回值(隐式返回空元组)
    }
    
    // 另一个入口函数示例
    public entry fun mint_tokens(
        admin: &signer,
        to: address,
        amount: u64
    ) {
        // 铸造代币逻辑
        // 需要管理员权限
    }
    
    // 普通公共函数 - 只能被其他Move代码调用
    public fun calculate_reward(amount: u64, rate: u64): u64 {
        amount * rate / 100
    }
    
    // 可以同时是public和entry
    public entry fun public_entry_func() {
        // 这个函数既可以被其他Move模块调用
        // 也可以直接从外部交易调用
    }
}
入口函数的限制:
  1. 必须是public函数
  2. 不能有显式返回值(返回()
  3. 通常第一个参数是&signer(但不是强制的)
  4. 所有参数必须是具体类型(不能是泛型)
为什么使用entry
  • 明确性:清晰表明哪些函数是外部接口
  • 工具支持:IDE和工具可以识别入口函数
  • 最佳实践:鼓励良好的模块设计

实战练习:编写一个数学工具模块

现在,让我们综合运用所学知识,编写一个实用的数学工具模块。
// 文件:sources/math_utils.move
module math_utils::math {
    use std::debug;
    
    // ========== 最大公约数(GCD) ==========
    
    // 内部实现函数(私有)
    fun gcd_internal(a: u64, b: u64): u64 {
        let mut x = a;
        let mut y = b;
        
        while (y != 0) {
            let remainder = x % y;
            x = y;
            y = remainder;
        };
        
        x  // 最大公约数
    }
    
    // 公共计算函数
    public fun gcd(a: u64, b: u64): u64 {
        gcd_internal(a, b)
    }
    
    // 入口函数(方便测试)
    public entry fun calculate_gcd(a: u64, b: u64) {
        let result = gcd(a, b);
        debug::print(&b"GCD: ");
        debug::print(&result);
    }
    
    // ========== 最小公倍数(LCM) ==========
    
    public fun lcm(a: u64, b: u64): u64 {
        if (a == 0 || b == 0) {
            return 0
        };
        
        // LCM(a, b) = a * b / GCD(a, b)
        a / gcd(a, b) * b  // 先除后乘避免溢出
    }
    
    public entry fun calculate_lcm(a: u64, b: u64) {
        let result = lcm(a, b);
        debug::print(&b"LCM: ");
        debug::print(&result);
    }
    
    // ========== 阶乘 ==========
    
    public fun factorial(n: u64): u64 {
        if (n == 0) {
            return 1
        };
        
        let mut result: u64 = 1;
        let mut i: u64 = 1;
        
        while (i <= n) {
            // 检查乘法是否会导致溢出
            // 在实际合约中,你可能需要更严谨的检查
            result = result * i;
            i = i + 1;
        };
        
        result
    }
    
    public entry fun calculate_factorial(n: u64) {
        let result = factorial(n);
        debug::print(&b"Factorial: ");
        debug::print(&result);
    }
    
    // ========== 判断质数 ==========
    
    public fun is_prime(n: u64): bool {
        if (n <= 1) {
            return false
        };
        
        if (n == 2) {
            return true
        };
        
        if (n % 2 == 0) {
            return false
        };
        
        // 检查奇数因子
        let mut divisor: u64 = 3;
        while (divisor * divisor <= n) {
            if (n % divisor == 0) {
                return false
            };
            divisor = divisor + 2;  // 只检查奇数
        };
        
        true
    }
    
    public entry fun check_prime(n: u64) {
        let result = is_prime(n);
        if (result) {
            debug::print(&b"是质数");
        } else {
            debug::print(&b"不是质数");
        };
    }
    
    // ========== 斐波那契数列 ==========
    
    public fun fibonacci(n: u64): u64 {
        if (n <= 1) {
            return n
        };
        
        let mut prev: u64 = 0;
        let mut curr: u64 = 1;
        let mut i: u64 = 2;
        
        while (i <= n) {
            let next = prev + curr;
            prev = curr;
            curr = next;
            i = i + 1;
        };
        
        curr
    }
    
    public entry fun calculate_fibonacci(n: u64) {
        let result = fibonacci(n);
        debug::print(&b"Fibonacci: ");
        debug::print(&result);
    }
    
    // ========== 单元测试 ==========
    
    #[test]
    fun test_gcd() {
        assert!(gcd(48, 18) == 6, 1001);
        assert!(gcd(17, 13) == 1, 1002);
        assert!(gcd(0, 5) == 5, 1003);
        assert!(gcd(7, 0) == 7, 1004);
    }
    
    #[test]
    fun test_lcm() {
        assert!(lcm(4, 6) == 12, 2001);
        assert!(lcm(21, 6) == 42, 2002);
        assert!(lcm(0, 5) == 0, 2003);
    }
    
    #[test]
    fun test_factorial() {
        assert!(factorial(0) == 1, 3001);
        assert!(factorial(1) == 1, 3002);
        assert!(factorial(5) == 120, 3003);
    }
    
    #[test]
    fun test_is_prime() {
        assert!(is_prime(2) == true, 4001);
        assert!(is_prime(17) == true, 4002);
        assert!(is_prime(15) == false, 4003);
        assert!(is_prime(1) == false, 4004);
    }
    
    #[test]
    fun test_fibonacci() {
        assert!(fibonacci(0) == 0, 5001);
        assert!(fibonacci(1) == 1, 5002);
        assert!(fibonacci(6) == 8, 5003);
        assert!(fibonacci(10) == 55, 5004);
    }
}
使用这个模块:
# 1. 编译
aptos move compile

# 2. 运行测试
aptos move test

# 3. 发布到测试网
aptos move publish

# 4. 调用入口函数
aptos move run \
  --function-id 'math_utils::math::calculate_gcd' \
  --args 'u64:48' 'u64:18'

📝 本章总结

恭喜!你已经掌握了Move语言的基础语法: 基本数据类型:整型、布尔值、地址、signer、vector
流程控制:条件语句、循环、控制流语句
模块与函数:模块组织、函数定义、可见性、入口函数
关键收获:
  1. 类型安全:Move是静态类型语言,编译时会进行严格检查
  2. 资源意识:虽然本章没有涉及资源,但数据类型的选择会影响后续的资源设计
  3. 模块化思维:通过模块组织代码,通过可见性控制访问权限
  4. 入口点设计entry函数是合约与外部世界的桥梁
下一步挑战:
  1. 尝试修改数学工具模块,添加更多函数(如计算平方根、幂运算)
  2. 创建自己的工具模块,包含处理vector的实用函数
  3. 尝试使用泛型编写更通用的函数
常见陷阱提醒:
  1. 忘记if语句结尾的分号
  2. 在循环中忘记更新循环变量,导致无限循环
  3. 混淆signeraddress的使用场景
  4. 访问vector时未检查索引边界
在下一章,我们将进入Move最核心、最独特的部分——结构体与能力系统。这是Move安全性的基石,也是与其他智能合约语言最不同的地方。准备好学习Move的”灵魂”了吗?
👨‍🍳 厨师笔记:
当我第一次学习这些基础语法时,最让我困惑的是signer类型。为什么需要这么复杂的权限系统?但当我开始编写真正的合约时,我意识到这正是Move的优雅之处——权限在类型系统中得到体现,而不是通过隐式的msg.sender。这种设计让安全漏洞在编译期就被发现,而不是在运行时造成损失。
记住:好的基础是成为大厨的关键。花时间理解这些概念,编写小练习,你会发现在后续的复杂合约开发中事半功倍。如果有任何疑问,随时回到本章复习。
休息一下,准备好进入Move最精彩的部分! 🎯