三、Move 函数

  • Ch1hiro
  • 更新于 2024-11-22 19:03
  • 阅读 347

本篇文章介绍了如何定义和调用函数

Sui_logo.png

1、函数 Function

1.1、函数 Function

​ 函数是 Move 语言中的代码构建块,它们被用于定义模块的逻辑和行为。在Move 语言中,函数是可重用的代码单元,可以在其他函数中调用或作为执行的入口点。本文将全面介绍 Move 语言中函数的概念、语法、使用方式以及相关实践。

1.2、函数声明

Move语言中,使用 fun 关键字来定义函数。函数声明包括可见性修饰符、入口修饰符(可选)、函数名称、类型参数(可选)、参数列表、返回类型和函数体。一个基本的函数声明语法如下:

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

下面是一个名为 foo 的函数,类型参数 T1T2 表示范型,函数返回值为元组,函数体并未实现复杂逻辑,而是直接把入参返回。

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

1.3、可见性修饰符

​ 默认情况下,模块中的函数只能在同一模块内部被调用,这种内部函数也称为私有函数。要允许其他模块访问,必须使用 publicpublic(package) 修饰符声明函数为公共可见。

  • public: 公共函数可以被任何模块中的函数调用,也可作为执行入口点。
  • public(package): 包公共函数仅可以被同一包(相同地址)中的其他模块调用,不可以被其他包调用。它是 MOVE 语言的新特性,之前同样功能的 public(friend) 语法已被废弃。
module hello02::target {
    public fun foo(): u64 { 10 }

    public(package) fun bar(): u64 { 20 }
}

// 可以从另一模块调用`foo`
module hello02::caller {
    use std::debug;

    #[test]
    fun test_calls_target() {
        let res1 = hello02::target::foo();
        let res2 = hello02::target::bar();
        debug::print(&res1);
        debug::print(&res2);
    }
}

1.4、函数调用

在调用函数时,需要为每个参数提供对应的实参值。实参可以是字面量,也可以是其他表达式的结果。

module hello02::example {
    public fun foo(x: u64): u64 { x }
    public fun bar(x: u64, y: u64): u64 { x + y }
}

module hello02::other {
    fun calls() {
        hello02::example::foo(42);      // 字面量实参
        hello02::example::bar(1, 2 * 3);// 表达式实参
    }
}

对于泛型函数,可以显式提供类型参数,也可以让编译器自动推断

module hello02::example02 {
    public fun id<T>(x: T): T { x }
}

module hello02::other02 {
    fun calls() {
            // 推断T为u64
        hello02::example02::id(0);        
         // 显式指定T为u256
        hello02::example02::id<u256>(0);  
    }
}

调用函数时,可以使用完全限定名(包括模块地址)或别名(通过use引入)

1.5、返回值

​ 函数体中最后一个表达式的值就是函数的返回值。Move 中通过函数末尾的表达式代替了return语句。如果函数体为空,则返回()。同时Move支持多返回值,通过元组类型声明,并在函数体中返回相应的元组表达式。

注意:表达式的结尾没有分号。这一点与很多其他语言不同。

fun double(x: u64): u64 {
   x + x  // 这个表达式的值就是函数返回值
}

fun do_nothing(): () {}  // 返回()

fun get_name_and_age(): (vector<u8>, u8) {
    (b"Alice", 25)
}

调用函数时,需要使用解构赋值来获取各个返回值,如果只需要部分返回值,可以使用_忽略其他值

let (name, age) = get_name_and_age();
let (name, _) = get_name_and_age();

Move 还支持return关键字显式返回,这在提前退出复杂的控制流时很有用:

fun index_of<T>(v: &vector<T>, target: &T): Option<u64> {
    let mut i = 0;
    let n = vector::length(v);
    while (i < n) {
            // 显式返回
        if (vector::borrow(v, i) == target) return option::some(i);
        i = i + 1
    };
    option::none()
}

代码解释:遍历向量数组,如果向量 v 的第 i 个值等于 target,则返回 i

1.6、示例代码

module hello::funcs_module {    
    /// 计算两个u64数值的和
    public fun add(x: u64, y: u64): u64 {
        x + y
    }

    /// 返回传入字符串的长度
    public fun string_length(s: vector<u8>): u64 {
        vector::length(&s)
    }

    /// 返回传入vector中最大值和最小值
    public fun vector_min_max(v: &mut vector<u64>): (u64, u64) {
        let mut min = vector::pop_back(v);
        let mut max = min;

        loop {
            if (vector::is_empty(v)) break;

            let val = vector::pop_back(v);
            if (val < min) min = val;
            if (val > max) max = val
        };

        (min, max)
    }

    #[test]
    public fun test_vector_funcs() {
        assert!(add(1, 2) == 3, 0);
        assert!(string_length(b"hello") == 5, 0);

        let mut v = vector[1, 5, 3, 7, 2];
        let (min, max) = vector_min_max(&mut v);
        assert!(min == 1, 0);
        assert!(max == 7, 0);
    }
}

2、表达式 Expression

2.1、表达式Expression

​ 表达式是一个代码单元,用于返回一个值。跟它相对的概念是语句(Statement),语句则更侧重于执行一些操作或控制程序流程,而不一定返回值

​ 在 Move 语言中,大多数语法结构都被设计为表达式,比如:字面量、运算符操作、函数调用及代码块,而 let 语句用于绑定一个值到一个变量,就属于语句而非表达式。总的来说,在Move语言中,除了let语句(声明语句)之外,几乎所有内容都是表达式。

注意:区分语句和表达式的一个简单方法就是有无分号;,有的话就是语句,执行操作但不返回结果;没有的话就是表达式,执行计算并返回结果。

2.2、字面量(Literals)

​ 在 Move 语言中,字面量是表达式的一种基本形式。Move支持多种类型的字面量,包括布尔值、整数、十六进制整数、字节数组等。这些字面量可以被用作变量的初始值,或者作为函数参数传递。例如:

let b = true;     // true是一个字面量
let n = 1000;     // 1000是一个字面量 
let h = 0x0A;     // 0x0A是一个字面量
let v = b"hello"; // b"hello"是一个字节向量字面量
let x = x"0A";    // x"0A"是一个字节向量字面量
let c = vector[1, 2, 3]; // vector[1, 2, 3]是一个向量字面量

2.3、运算符

算术运算符、逻辑运算符和位运算符用于对值执行运算。运算的结果是一个值,因此运算符本身也是一种表达式

let sum = 1 + 2;   // 1 + 2是一个表达式
let sum = (1 + 2); // 同一个表达式,使用了括号
let is_true = true && false; // true && false是一个表达式  
let is_true = (true && false); // 同一个表达式,使用了括号

2.4、代码块

​ 代码块使用一对花括号 {} 来表示,它是一系列语句和表达式的集合,它返回块中最后一个表达式的值,如果没有表达式,编译器会自动插入一个单元值()——一个空的表达式。简单来说,我们可以把带分号;的命令行视为语句,反之视为表达式

// 一个包含let语句和表达式的代码块
let sum = {
    let a = 1; 
    let b = 2;
    a + b // 这个表达式的值就是代码块的返回值
};

let none = {
    let a = 1;
    let b = 2;
    a + b; // 分号结尾,不会返回这个值
    // 编译器会自动插入一个空表达式`()`
};

2.5、函数调用

​ 函数调用 add(1, 2) 是一个表达式,它调用一个函数并返回函数体中最后一个表达式的值。在 add 函数内部,a + b 算术运算也是一个表达式。

fun add(a: u8, b: u8): u8 {
    a + b
}

#[test]
fun some_other() {
    let sum = add(1, 2); // add(1, 2)是一个表达式,类型为u8
}

2.7、示例代码

module hello::expressions_module {

    // 字面量表达式
    const OFFSET: u8 = 32;
    const FLAG: bool = true;

    // 函数定义及调用
    fun add(a: u64, b: u64): u64 { a + b }

    public fun examples(x: u64, y: u64): u64 {
        let is_gt = x > y; // 运算符表达式

        // 代码块和let绑定 
        let z = {
            let tmp = if (is_gt) { 
                x + (OFFSET as u64) // 常量字面量
            } else {
                y           // 变量表达式
            };
            add(tmp, y)     // 函数调用
        };

        // 向量字面量
        let vec = vector[1, 2, 3];

        // 字节向量字面量
        let bytes = b"hello";
                // z 后面没有分号,作为表达式返回
        z
    }
}

3、控制流

3.1、控制流

​ 控制流对于编程语言来说是很重要的。控制流语句控制程序中的执行流程,包括做出决策、重复代码块以及提前退出代码块,决定了整个应用的流程。

Move 中的几个控制流语句:

  1. ifif-else :决定是否执行代码块
  2. loopwhile :重复代码块
  3. breakcontinue 语句: 提前退出循环
  4. return 语句:提早退出函数,并返回值

3.2、条件语句

ifif-else 是条件语句的关键词,并基于 boolean 来决定是否执行代码块,当 if-else 备用上时,基于 boolean 来决定该执行此代码块,或其他代码块。以下是条件语句的语法:

if (<bool_expression>) <expression>;
if (<bool_expression>) <expression> else <expression>;

3.3、循环语句

​ 循环是应用重要的部分。它们重复执行任务,实现自动化、数据处理和数据集合的迭代。需要注意的是,执行循环任务可能会导致无限循环。在区块链上,也可能会因此导致 gas 耗尽而使交易被终止。

Move 有两种内置循环类型: whileloop 。以下是循环语句的语法:

while (<bool_expression>) { <expressions>; };
loop { <expressions>; };

3.4、退出循环或跳过迭代

​ 循环可以透过重复的执行来达到我们要的结果。当条件达到后,就可以退出循环。这时,我们可以用 break 退出循环。

如果要跳过迭代,进行到下一个代码块,那我们就用 continue

breakcontinue 可以和 loop 还有 while 一起用。

3.5、返回

在特定的结果达成后,可以用 return 提早退出函数,并返回一个值。语法如下:

return <expression>

3.6、示例代码

module hello::hello_module {
    //条件语句
    #[test]
      fun test_if_else() {
          let x = 5;
          let y = if (x > 0) {
              1   //预期结果
          } else {
              0  
          };
          assert!(y == 1, 0);
      }

    #[test]
    fun test_break_loop() {
        let mut x = 0;

        // 这将循环直到“x”等于 5。
        loop {
            x = x + 1;

            // 如果 `x` 是 5,则退出循环。
            if (x == 5) {
                break // 退出循环
            }
        };

        assert!(x == 5, 0);
    }

    #[test]
    fun test_continue_loop() {
        let mut x = 0;

        // 这将循环直到“x”等于 10.
        loop {
            x = x + 1;

            // 如果“x”是奇数,则跳过其余的迭代。
            if (x % 2 == 1) {
                continue // 跳过剩余的迭代.
            };

            std::debug::print(&x);

            // 如果“x”是10,则退出循环。
            if (x == 10) {
                break // 推出循环。
            }
        };

        assert!(x == 10, 0); // 10
    }

    /// 如果 `x` 大于 0 且不为 5,则此函数返回 `true`,
    /// 否则返回“false”。
    fun is_positive(x: u8): bool {
        if (x == 5) {
            return false
        };

        if (x > 0) {
            return true
        };

        false
    }

    #[test]
    fun test_return() {
        assert!(is_positive(5) == false, 0);
        assert!(is_positive(0) == false, 0);
        assert!(is_positive(1) == true, 0);
    }
}

4、结构方法

4.1、结构方法

​ 和其他许多语言一样, Move 编译器允许定义可以在结构体实例上调用的方法 (method)。这让结构体的字段更容易地在函数内操作。

4.2、语法

​ 如果函数的第一个参数是同一个模块的结构体,那么简单的用 . 就可以调用该方法。不然, 就需要用标准函数调用语法:

ModuleName::function_name<TypeArguments>(arguments)

4.3、方法别名

​ 为了避开名称冲突,或帮助取一个好记的名字,我门可以为方法在输入时取个别名。要注意的是,如果结构体不是在同一个模块,别名的可见性不能是 public

// 用于本地方法关联
use fun function_path as Type.method_name;

// 以别名导出
public use fun function_path as Type.method_name;

4.4、示例代码

module hello::hero {
    /// 代表hero的结构体。
    public struct Hero has drop {
        health: u8,
        mana: u8,
    }

    /// 创建新hero
    public fun new(): Hero { Hero { health: 100, mana: 100 } }

    /// 施展法术的一种方法,消耗mana。
    public fun heal_spell(hero: &mut Hero) {
        hero.health = hero.health + 10;
        hero.mana = hero.mana - 10;
    }

    /// 返回hero健康值的方法。
    public fun health(hero: &Hero): u8 { hero.health }

    /// 返回hero法力值的方法。
    public fun mana(hero: &Hero): u8 { hero.mana }

    #[test]
    // 测试 `Hero` 结构的方法。
    fun test_methods() {
        let mut hero = new();
        hero.heal_spell();

        assert!(hero.health() == 110, 1);
        assert!(hero.mana() == 90, 2);
    }
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ch1hiro
Ch1hiro
一名Web3爱好者