表达式和作用域

在编程语言中,表达式是具有返回值的代码单元。有返回值的函数调用是一个表达式,它有返回值;整型常数也是一个表达式,它返回整数;其它表达式依此类推。

表达式必须用分号";"隔开

空表达式

类似于 Rust,Move 中的空表达式用空括号表示:

module book::exp_scope {
    fun empty() {
        () // this is an empty expression
    }
}

文字(Literal)表达式

下面的代码,每行包含一个以分号结尾的表达式。最后一行包含三个表达式,由分号隔开。

module book::exp_scope {
    fun main() {
        10;
        10 + 5;
        true;
        true != false;
        0x1;
        1; 2; 3;
    }
}

现在我们已经知道了最简单的表达式。但是为什么我们需要它们?以及如何使用它们?这就需要介绍 let 关键字了。

变量和let关键字

关键字 let 用来将表达式的值存储在变量中,以便于将其传递到其它地方。我们曾经在基本类型章节中使用过 let,它用来创建一个新变量,该变量要么为空(未定义),要么为某表达式的值。

module book::exp_scope {
    fun main() {
        let a;
        let b = true;
        let c = 10;
        let d = 0x1;
        a = c;
    }
}

关键字 let 会在当前作用域内创建新变量,并可以选择初始化此变量。该表达式的语法是:let : ;或let =

创建和初始化变量后,就可以使用变量名来修改访问它所代表的值了。在上面的示例中,变量 a 在函数末尾被初始化,并被分配了一个值 c。

等号"="是赋值运算符。它将右侧表达式赋值给左侧变量。示例:a = 10 表示将整数10赋值给变量a。

整型运算符

Move具有多种用于修改整数值的运算符:

运算符操作类型
+sumuintLHS加上RHS
-subuint从LHS减去RHS
/divuint用LHS除以RHS
*muluintLHS乘以RHS
%moduintLHS除以RHS的余数
<<lshiftuintLHS左移RHS位
>>rshiftuintLHS右移RHS位
&anduint按位与
^xoruint按位异或
\oruint按位或

LHS - 左侧表达式, RHS - 右侧表达式; uint: u8, u64, u128.

下划线 "_" 表示未被使用

Move 中每个变量都必须被使用,否则代码编译不会通过, 因此我们不能初始化一个变量却不去使用它。但是你可以用下划线来告诉编译器,这个变量是故意不被使用的。

例如,下面的脚本在编译时会报错:

module book::exp_scope {
    fun main() {
        let a = 1;
    }
}

报错:


    ┌── /scripts/script.move:3:13 ───
    │
 33 │         let a = 1;
    │             ^ Unused assignment or binding for local 'a'. Consider removing or replacing it with '_'
    │

编译器给出明确提示:用下划线来代替变量名。

module book::exp_scope {
    fun main() {
        let _ = 1;
    }
}

屏蔽

Move 允许两次定义同一个的变量,第一个变量将会被屏蔽。但有一个要求:我们仍然需要"使用"被屏蔽的变量。

module book::exp_scope {
    fun main() {
        let a = 1;
        let a = 2;
        let _ = a;
    }
}

在上面的示例中,我们仅使用了第二个a。第一个a实际上未使用,因为a在下一行被重新定义了。所以,我们可以通过下面的修改使得这段代码正常运行。

module book::exp_scope {
    fun main() {
        let a = 1;
        let a = a + 2;
        let _ = a;
    }
}

块表达式

块表达式用花括号"{}"表示。块可以包含其它表达式(和其它代码块)。函数体在某种意义上也是一个代码块。

module book::exp_scope {
    fun block() {
        { };
        { { }; };
        true;
        {
            true;

            { 10; };
        };
        { { { 10; }; }; };
    }
}

作用域

Wikipedia 中所述,作用域是绑定生效的代码区域。换句话说,变量存在于作用域中。Move 作用域是由花括号扩起来的代码块,它本质上是一个块。

定义一个代码块,实际上是定义一个作用域。

module book::exp_scope {
    fun scope_sample() {
        // this is a function scope
        {
            // this is a block scope inside function scope
            {
                // and this is a scope inside scope
                // inside functions scope... etc
            };
        };

        {
            // this is another block inside function scope
        };
    }
}

从该示例可以看出,作用域是由代码块(或函数)定义的。它们可以嵌套,并且可以定义多个作用域,数量没有限制。

变量的生命周期和可见性

我们前面已经介绍过关键字 let 的作用,它可以用来定义变量。有一点需要强调的是,该变量仅存在于变量所处的作用域内。也就是说,它在作用域之外不可访问,并在作用域结束后立即消亡。

module book::exp_scope {
    fun let_scope_sample() {
        let a = 1; // we've defined variable A inside function scope

        {
            let b = 2; // variable B is inside block scope

            {
                // variables A and B are accessible inside
                // nested scopes
                let c = a + b;

            }; // in here C dies

            // we can't write this line
            // let d = c + b;
            // as variable C died with its scope

            // but we can define another C
            let c = b - 1;

        }; // variable C dies, so does C

        // this is impossible
        // let d = b + c;

        // we can define any variables we want
        // no name reservation happened
        let b = a + 1;
        let c = b + 1;

    } // function scope ended - a, b and c are dropped and no longer accessible
}

变量仅存在于其作用域(或代码块)内,当作用域结束时变量随之消亡。

块返回值

上面我们了解到代码块是一个表达式,但是我们没有介绍为什么它是一个表达式以及代码块的返回值是什么。

代码块可以返回一个值,如果它后面没有分号,则返回值为代码块内最后一个表达式的值。

听起来不好理解,我们来看下面的例子:

module book::exp_scope {
    fun block_ret_sample() {

        // since block is an expression, we can
        // assign it's value to variable with let
        let a = {

            let c = 10;

            c * 1000  // no semicolon!
        }; // scope ended, variable a got value 10000

        let b = {
            a * 1000  // no semi!
        };

        // variable b got value 10000000

        {
            10; // see semi!
        }; // this block does not return a value

        let _ = a + b; // both a and b get their values from blocks
    }
}

代码块中的最后一个表达式(不带分号)是该块的返回值。

小结

我们来总结一下本章要点:

  1. 每个表达式都必须以分号结尾,除非它是 block 的返回值。
  2. 关键字 let 使用值或表达式创建新变量,该变量的生命周期与其作用域相同。
  3. 代码块是一个可能具有也可能没有返回值的表达式。

下一章我们将介绍控制流和分支语句。

进一步阅读