当前位置:嗨网首页>书籍在线阅读

03-代码块和表达式

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

7.1.1 代码块和表达式

尽管语句和表达式混合,但Rust主要是一种面向表达式的语言。这意味着大多数构造都是一个包含返回值的表达式。它也是一种使用类C语言风格花括号的语言,以便在程序中划定变量的作用域。

在本章后续内容深入介绍它们之前,让我们对它们进行简要介绍。

代码块表达式(以下简称代码块)是以“{”开头,以“}”结尾的任意元素,在Rust中,它们包含if else表达式、match表达式、while循环、循环、裸代码块、函数、方法及闭包。使用它们后通常都会返回一个值,该值是表达式的最后一行。如果在最后一个表达式中放置一个分号,那么代码块表达式的返回值默认是unit()类型的。

一个和代码块相关的概念是作用域。只要创建新的代码块,就会引入作用域的概念。当我们新建一个代码块,并且在其中创建任何变量绑定时,该绑定的有效区域将仅限于该代码块,而且我们对它们的任何引用也仅限于在该作用域之内有效。这就像一个新建变量的生存环境,它会与其他变量隔离开。

在Rust中,诸如函数、impl代码块、裸代码块、if else表达式、match表达式、函数及闭包这类元素会引入新的作用域。

在代码块/作用域内部,我们可以声明结构体、枚举、模块、特征及其实现,甚至包括代码块。

每个Rust程序都是以一个根作用域开始的,这是main函数引入的作用域。在其中还可以创建许多嵌套作用域。main作用域成为其内部声明的所有作用域的父作用域。请考虑如下代码:

// scopes.rs
fn main() {
    let mut b = 4;
    {
        let mut a = 34 + b;
        a += 1;
    }
    b = a;
}

我们使用一个裸代码块来引入一个新的内部作用域,并在其中创建一个变量a。在上述作用域结束之后,我们尝试将a的值分配给b,该值来自内部作用域。Rust会抛出一个编译时错误,提示此作用域内找不到变量a的值。来自main的父作用域不知道关于a的任何内容,因为它来自内部作用域。作用域的这个属性也用于控制某些引用的有效时间,正如我们在第5章中看到的那样。

但内部作用域可以访问其父作用域中的值。因此,我们可以在内部作用域中构造“34+b”这样的代码。

接下来将讨论表达式,我们可以从它们的返回值属性中受益,并且所有分支中必须具有相同的类型,这可以构造非常简洁的代码。例如,请考虑如下代码片段:

// block_expr.rs
fn main() {
    // 使用裸代码块同时执行多个任务
    let precompute = {
        let a = (-34i64).abs();
        let b = 345i64.pow(3);
        let c = 3;
        a + b + c
    };
    // match表达式
    let result_msg = match precompute {
        42 => "done",
        a if a % 2 == 0 => "continue",
        _ => panic!("Oh no !")
    };
    println!("{}", result_msg);
}

我们可以使用裸代码块将几行代码组合到一起,之后在末尾分配值,并使用a+b+c表达式的隐式返回值进行预计算。match表达式还可以直接从其匹配臂分配和返回值。

113.png 注意

与C语言中的switch语句类似,Rust中的匹配臂不会受到字母大小写的影响,这会导致C代码中的许多错误。

在C语言的switch分支中,如果我们想要在运行某些代码后退出,就需要在switch代码块中的每个case语句后都设置一个中断(break)。如果没有设置中断,那么case语句后面的任何代码会被执行,这被称为落空(fall-through)行为。另外,match表达式能够保证仅执行一个匹配臂中的代码。

if else表达式提供了类似的简洁性:

// if_expr.rs
fn compute(i: i32) -> i32 {
    2 * i
}
fn main() {
    let result_msg = "done";
    // if表达式赋值
    let result = if result_msg == "done" {
        let some_work = compute(8);
        let stuff = compute(4);
        compute(2) + stuff //最后一个表达式结果分配给result
    } else {
        compute(1)
    };
    println!("{}", result);
}

在基于语句的语言中(例如Python),你可以为前面的代码片段编写类似的内容:

result = None
if (state == "continue"):
    let stuff = work()
    result = compute_next_result() + stuff
else:
    result = compute_last_result()

在Python代码中,我们必须事先声明result变量,然后在if else的某个分支中进行单独赋值。在这里Rust更简洁,赋值是由if else表达式完成的。此外,在Python中,你可能会忘记为某个分支中的变量赋值,并且该变量可能未被初始化。如果你从if代码块返回并分配一些内容,然后错过或从else代码块返回另一种类型,那么Rust将在编译时预警。

此外,Rust还支持声明未初始化的变量:

fn main() {
    let mut a: i32;
    println!("{:?}", a);         // 错误
    a = 23;
    println!("{:?}", a);         // 正常
}

但是我们在使用它们之前需要先对其初始化。如果后续尝试读取未初始化的变量,那么Rust将禁止该操作,并在编译期提示用户该变量必须初始化:

   Compiling playground v0.0.1 (file:///playground)
error[E0381]: use of possibly uninitialized variable: `a`
 --> src/main.rs:7:22
  |
7 |     println!("{:?}", a);
  |                      ^ use of possibly uninitialized `a`