Skip to content

表达式和作用域

在 Move 语言中,表达式 是一段会计算出值的代码。除了 let 声明语句以外,几乎所有代码都可以被看作是表达式。作用域 则定义了变量在代码中的有效范围。

表达式类型

Move 语言支持多种类型的表达式,包括:

  • 字面量: 字面量是直接写在代码中的值。例如:
    • truefalse 代表布尔值。
    • 01123123 或其他数字代表整数值。
    • 0x00x10x123 或其他十六进制数代表整数值。
    • b"bytes_vector" 代表字节向量值。
    • x"0A" 代表字节值的十六进制字面量。
  • 运算符: Move 支持算术、逻辑和位运算符。例如 +-*/%&&||<<>> 等等。运算符对值进行操作,其结果也是一个值,所以运算符本身也是表达式。
  • 代码块: 代码块是用大括号 {} 包围的代码序列。代码块也是一种表达式,它返回最后一个表达式的值。
  • 函数调用: 函数调用也是一种表达式,它会执行函数并返回函数的返回值。
  • 控制流表达式: 控制流表达式用于控制程序的执行流程。例如 if 表达式、while 循环等等。控制流表达式也是表达式,并返回一个值。

示例:

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[]; // vector[] 是一个向量字面量

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

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

作用域

作用域 是指变量在代码中有效的范围。

  • 在函数中声明的变量,其作用域就是该函数内部。
  • 在代码块中声明的变量,其作用域就是该代码块内部。

当程序执行到作用域的末尾时,该作用域内声明的变量就会被销毁。

所有权和作用域

在 Move 语言中,每个变量都有一个所有者,通常是定义该变量的作用域。当所有者作用域结束时,该变量就会被销毁。

示例:

move
module book::ownership; 

public fun owner() {
    let a = 1; // a 属于 `owner` 函数
} // a 在此处被销毁

public fun other() {
    let b = 2;  // b 属于 `other` 函数
} // b 在此处被销毁

#[test]
fun test_owner() {
    owner();
    other();
   // a 和 b 在此处无效
}

在上面的示例中,变量 a 属于 owner 函数,变量 b 属于 other 函数。当每个函数被调用时,变量被定义,当函数结束时,变量被销毁。

  • 返回值: 如果一个函数返回一个变量,那么该变量的所有权就会被转移到函数的调用者。

示例:

move
module book::ownership; 

public fun owner(): u8 {
    let a = 1; // a 在此处被定义
    a // 作用域结束,a 被返回
} 

#[test] 
fun test_owner() {
    let a = owner(); // a 在此处有效
} // a 在此处被销毁
  • 传值: 如果一个变量被作为参数传递给另一个函数,那么该变量的所有权就会被转移到该函数。这也被称为 移动语义。

示例:

move
module book::ownership; 

public fun owner(): u8 {
    let a = 10; 
    a // a 被返回
} 

public fun take_ownership(v: u8) {
   // v 属于 `take_ownership`
} // v 在此处被销毁

#[test] 
fun test_owner() {
    let a = owner();
    take_ownership(a); 
    // a 在此处无效
}
  • 代码块的作用域: 每个函数都有一个主作用域,它还可以通过使用代码块拥有子作用域。代码块是一系列语句和表达式,它有自己的作用域。在代码块中声明的变量属于该代码块,当代码块结束时,变量被销毁。

示例:

move
module book::ownership; 

public fun owner() {
    let a = 1; // a 属于 `owner` 函数的作用域
    {
        let b = 2; // b 属于代码块
        {
            let c = 3; // c 属于代码块
        }; // c 在此处被销毁
    }; // b 在此处被销毁
    // a = b; // 错误:b 在此处无效
    // a = c; // 错误:c 在此处无效
} // a 在此处被销毁

但是,如果我们使用代码块的返回值,则变量的所有权将转移给代码块的调用者。

示例:

move
module book::ownership; 

public fun owner(): u8 {
    let a = 1; // a 属于 `owner` 函数的作用域
    let b = { 
        let c = 2; // c 属于代码块
        c // c 被返回
    }; // c 在此处被销毁
    a + b // a 和 b 在此处都有效
}

可复制类型

一些 Move 中的类型是 可复制的,这意味着它们可以在不转移所有权的情况下被复制。这对于体积小且复制成本低的类型很有用,例如整数和布尔值。当可复制类型被传递给函数或从函数返回时,或者当它们被移动到一个作用域然后在原始作用域中被访问时,Move 编译器会自动复制这些类型。

总结

  • 表达式是计算出值的代码片段。
  • 作用域定义了变量在代码中的有效范围。
  • 变量的所有权通常与其作用域相关联。
  • 可复制类型可以在不转移所有权的情况下被复制。

理解表达式和作用域的概念对于编写正确的 Move 代码至关重要,因为它有助于我们理解变量的生命周期和数据所有权的转移。