Rust惑点启示系列(四):到处都是的大括号

发布时间:2024-10-14 15:16
最后更新:2024-10-14 22:28
所属分类:
Rust

Rust中的大括号也是一个非常神奇的特点,它除了跟其他语言中的大括号一样可以用来形成一个语句块以外,还可以被用来实现很多功能。这些一般是其他语言做不到的。不过这也给Rust的代码阅读带来了一些复杂。

本文是《Rust惑点启示笔记》系列文章中的第四篇。这个系列的文章主要计划对Rust语言使用过程中经常会出现的一些容易迷惑的惑点进行一个启发式的讨论和记录。

本系列专题还有以下文章:

  1. Rust惑点启示系列(一):避免随意使用Clone
  2. Rust惑点启示系列(二):从函数中返回一些东西
  3. Rust惑点启示系列(三):引用的生命期从来就不够长
  4. Rust惑点启示系列(四):到处都是的大括号
  5. Rust惑点启示系列(五):工具类型太多了
  6. Rust惑点启示系列(六):如何下手编写一个函数
  7. Rust惑点启示系列(七):使用全局变量和单例
  8. Rust惑点启示系列(八):奇形怪状的Rust闭包

语句块是可以返回值的

Rust的语句块是可以返回一个值的。语句块中的最后一个表达式的运行结果就是语句块的返回值。

要使用语句块的这个特性,就需要特别注意Rust中的表达式和语句的区别。一个非常显而易见的区别就是,语句都是由;结尾的。语句在Rust中的返回值都是(),也就是一个Unit类型。例如以下两个表达式的区别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let k = {
  let a = 1;
  let b = 2;
  a + b
}; // k的值是3
let j = {
  let a = 1;
  let b = 2;
  a + b;
}; // j的值是()
println!("k: {:?}, j: {:?}", k, j);

这个示例的运行结果可以看出来,在Rust中语句和表达式之间的区别。

不过说到这里,有没有感觉这种用法实际上跟ifmatchfn这些语法结构中返回一个值的特性相似?的确,在与这些能够形成表达式的关键字搭配使用的时候,语句块可以将一组逻辑组合在一起,形成一个表达式。

然而实际上,Rust里的语句块就是由一组操作组成的表达式。

小心返回值类型陷阱

在用熟了从语句块中返回一个值以后,可能就会再ifmatch这样的逻辑分支处理中遇到一个返回值类型陷阱。这个陷阱其实说起来很简单,就是在不同的逻辑分支中返回不同类型的返回值。

Rust里没有联合类型,所以在一个表达式里返回多种类型的数据是不能通过编译器检查的。因为虽然有多个语句块,和多个逻辑处理分支,但是ifmatch这样的逻辑处理过程还是被认为是一个表达式的。所以Rust就会要求它们的所有逻辑分支必须返回相同的数据类型。

这个限制在编码的时候是需要注意的。

如果实在需要返回不同类型的值,可以考虑利用Rust的动态分发功能,返回一个Box<dyn Trait>类型的特征对象。这在一定程度上可以允许你构建不同类型的值。但是注意这种用法实际上会有一些运行开销,因为在调用方法的时候,需要使用指针在虚表(vtable)中进行定位。

语句块可以用来控制生命期

使用语句块来控制生命期其实很容易理解,因为语句块的形成,其内部引入了一个新的作用域,这就使得语句块内部的变量在语句块外面是不可见的。在语句块执行结束时,语句块中声明的变量都将会被释放掉。这样一来不就是控制了生命期嘛。

语句块是可以嵌套定义的,在内部语句块中声明的变量在语句块外部是不可见的,但相反,在一个语句块内部是可以访问其外部语句块的内容的。

根据Rust的作用域规则,在内部语句块中访问外部语句块中的内容,是通过引用借用的,而不是转移所有权的。不论在内部语句块中使用不可变引用还是可变引用来使用外部语句块中的内容,都是无需获取所使用内容的所有权的。但是在使用的时候需要注意Rust的借用规则。

例如以下示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
  let x = 1;

  {
    println!("Inside: {}", x); // 此处使用的实际上是外部变量x的引用,也就是&x。
  }

  {
    let y = &mut x; // 这里对x进行一个显式的可变借用,需要注意在同一时间可变借用只能存在一个。
    *y += 2;
  }

  println!("Outside: {}", x); // y已经在上面的语句块结束时被释放掉了,但是x的值已经被可变引用修改了。
}

可能在有些代码中看到类似move {}的语句块写法。move关键字的使用,会强制将语句块中捕获的外部变量的所有权转移到语句块内部。在不使用move关键字的时候,Rust是会根据被捕获变量在语句块中使用的方式来决定如何捕获它的,但是有了move关键字的参与,对于外部变量的捕获就强制变为捕获所有权了。

这种强制转移所有权的用法在构建多线程任务和异步任务的时候非常常见,这也跟多线程任务和异步任务要求拥有其所使用资源的所有权有关。

语句块标记

Rust中的语句块是可以带标记(label)的,标记一般使用'label: {}的格式放在大括号前面。如果被标记的语句块是一个循环,例如loopforwhile'label:需要放在循环关键字之前。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
  'outer: for i in 1..5 {
    for j in 1..5 {
      if i * j > 15 {
        break 'outer;
      }
      // 省略剩下的逻辑过程
    }
  }
}

语句块的标签实际上是用来配合breakcontinue语句来使用,达到的快速跳出循环的目的的。这也是Rust控制流管理的一个强大的工具。注意break跳出的是被标记循环,continue是跳出当前循坏,但继续执行被标记循环。

其实if这些语句也是可以使用语句块标记的,但是语句块标记不能直接放在关键词前面,而是需要在逻辑分支的语句块中,建立一个新的语句块来添加语句块标记。

索引标签
Rust
语句块
生命期
表达式