表达式

语法
表达式 :
      无块表达式
   | 块表达式

无块表达式 :
   外围属性*
   (
         字面值表达式
      | 路径表达式
      | 操作符表达式
      | 分组表达式
      | 数组表达式
      | Await表达式
      | 索引表达式
      | 元组表达式
      | 元组索引表达式
      | 结构体表达式
      | 调用表达式
      | 方法调用表达式
      | 字段表达式
      | 闭包表达式
      | Async块表达式
      | Continue表达式
      | Break表达式
      | 区间表达式
      | Return表达式
      | 下划线表达式
      | 宏调用表达式
   )

块表达式 :
   外围属性*
   (
         块表达式
      | Unsafe块表达式
      | Loop表达式
      | If表达式
      | IfLet表达式
      | Match表达式
   )

表达式具有两个作用: 总是产生一个值,并可能产生其他效果 (也称为 "副作用" )。 副作用在表达式计算为一个值的期间产生。 许多表达式包含子表达式,也称为表达式的操作数。表达式的具体语义决定了以下行为:

  • 在计算表达式时是否进一步计算操作数
  • 计算操作数的顺序
  • 如何将操作数的值组合以获得表达式的值

块仍是表达式的一种,因此块、语句、表达式和块可以递归嵌套。

注意: 给表达式的操作数命名可以便于讨论,但目前这些名称未稳定,可能会更改。

表达式优先级

Rust 运算符和表达式的优先级按照以下顺序进行排列,从强到弱。 在相同优先级的二元运算符中,根据其结合性得出分组顺序。

运算符/表达式结合性
路径
方法调用
字段表达式从左到右
函数调用、数组索引
?
一元 - * ! & &mut
as从左到右
* / %从左到右
+ -从左到右
<< >>从左到右
&从左到右
^从左到右
|从左到右
== != < > <= >=需要括号
&&从左到右
||从左到右
.. ..=需要括号
= += -= *= /= %=
&= |= ^= <<= >>=
从右到左
return break 闭包

操作数的求值顺序

以下表达式列表的所有表达式都以相同的方式对其操作数进行计算。 其他表达式要么不需要操作数,要么根据其各自的语法进行有条件的计算。

  • 解引用表达式
  • 错误传导表达式
  • 取反表达式
  • 算术和逻辑二元运算符
  • 比较运算符
  • 类型转换表达式
  • 分组表达式
  • 数组表达式
  • await 表达式
  • 索引表达式
  • 元组表达式
  • 元组索引表达式
  • 结构体表达式
  • 函数调用表达式
  • 方法调用表达式
  • 字段表达式
  • break 表达式
  • 范围表达式
  • return 表达式

这些表达式的操作数在产生表达式副作用之前进行求值。 取多个操作数的表达式按照源代码中从左到右的顺序进行求值。

注意: 其子表达式是否为一个表达式的操作数由前面的 "表达式优先级" 决定。

例如,两个 next 方法调用总是按照它们在代码中出现的顺序被调用:

#![allow(unused)]
fn main() {
// 在这个例子中使用 `Vec` 而不是数组,是为了避免引用,因为在编写此示例时,数组的迭代器特性还没有稳定。
let mut one_two = vec![1, 2].into_iter();
assert_eq!(
    (1, 2),
    (one_two.next().unwrap(), one_two.next().unwrap())
);
}

注意: 由于这个规则是递归的,所以这些表达式从内到外依次求值,直到没有更深层次的子表达式。在这个过程中会暂时忽略同级的表达式。

占位表达式和值表达式

表达式从求值的特性上可区分为: 占位表达式和值表达式,以及次要的可赋值表达式。 在表达式中,操作数可以出现在占位上下文中或值上下文中。

占位表达式 是表示内存地址的表达式。这些表达式以 路径 引用局部变量、 静态变量解引用数组索引 表达式 ( expr[expr] ) 、 字段 引用 (expr.f) 和带括号的占位表达式。其他表达式都是值表达式。

值表达式 是表示真实值的表达式。

以下为占位表达式上下文:

译注: 基础这个概念在多处被使用,类似于基类,但 rust 中没传统继承这概念,使用基类将可能引起读者混淆。

注意: 从历史上看,占位表达式称为左值,值表达式称为右值。

可赋值表达式 是指出现在 赋值 表达式的左操作数的表达式。 确切来说,可赋值表达式可以是:

在可赋值表达式中允许使用任意括号。

移动和复制类型

当在值表达式上下文中计算占位表达式,或者通过值在模式中绑定占位表达式时,它表示该内存位置所存储的值。 如果该值的类型实现了 Copy ,那么该值将被复制。 其它情况,如果该类型是 Sized,那么可能移动该值。 只有以下占位表达式可以 '移出' :

在计算时从局部变量的占位表达式中移动出后,该占位将被取消初始化,不能再次读取,直到重新初始化。 在其他情况,尝试在值表达式上下文中使用占位表达式将产生错误。

Mutability

要将一个占位表达式赋值、进行可变借用、进行隐式可变借用或绑定到包含 ref mut 的模式中时,则必须是可变的。 这被称为可变占位表达式,其他称为不可变占位表达式。

下面的表达式可以是可变占位表达式上下文:

  • 当前没有被借用的可变 变量
  • 可变 static 条目
  • 临时值
  • 字段: 这将在可变占位表达式上下文中计算子表达式。
  • *mut T 指针的 解引用
  • 类型为 &mut T 的变量或变量字段的解引用。注意:这是对下一条规则的例外情况。
  • 实现 DerefMut 的类型的解引用: 这要求被解引用的值在可变占位表达式上下文中求值。
  • 实现 IndexMut 的类型的 数组索引: 这将在可变占位表达式上下文中计算被索引的值,但不包括索引。

临时值

在大多数占位表达式上下文中使用值表达式时,会创建一个临时的无名内存位置,并将其初始化为该值。 表达式计算为该位置,除非它被 提升static 静态的。 临时值的 丢弃作用域 通常是封闭语句的末尾。

隐式借用

某些表达式将会通过隐式借用将一个表达式作为占位表达式。 比如,可以直接比较两个非固定大小的 切片 是否相等,因为 == 运算符会隐式地借用它的操作数:

#![allow(unused)]
fn main() {
let c = [1, 2, 3];
let d = vec![1, 2, 3];
let a: &[i32];
let b: &[i32];
a = &c;
b = &d;
// ...
*a == *b;
// 等价形式:
::std::cmp::PartialEq::eq(&*a, &*b);
}

隐式借用可以出现在以下表达式中:

trait 重载

许多以下的运算符和表达式可以使用 std::opsstd::cmp 中的 trait 重载为其他类型可用。 这些 trait 在 core::opscore::cmp 中也有相同的名称。

表达式属性

在表达式前面的 外围属性 只允许在以下几种情况下使用:

它们不允许在以下情况下使用: