表达式
语法
Expression :
ExpressionWithoutBlock
| ExpressionWithBlockExpressionWithoutBlock :
外部属性*†
(
字面量表达式
| 路径表达式
| 运算符表达式
| 分组表达式
| 数组表达式
| Await 表达式
| 索引表达式
| 元组表达式
| 元组索引表达式
| 结构体表达式
| 调用表达式
| 方法调用表达式
| 字段表达式
| 闭包表达式
| Async 块表达式
| Continue 表达式
| Break 表达式
| 范围表达式
| Return 表达式
| 下划线表达式
| 宏调用
)ExpressionWithBlock :
外部属性*†
(
块表达式
| Const 块表达式
| Unsafe 块表达式
| 循环表达式
| If 表达式
| If let 表达式
| Match 表达式
)
表达式可以扮演两个角色:它总是产生一个值,并且它可能产生效果(也称为“副作用”)。
表达式在求值时会求得一个值,并在此过程中产生效果。
许多表达式包含子表达式,这些子表达式被称为该表达式的操作数。
每种表达式的含义决定了几件事情:
- 求值表达式时是否要求值其操作数
- 求值操作数的顺序
- 如何组合操作数的值以获得表达式的值
通过这种方式,表达式的结构决定了执行的结构。块只是另一种类型的表达式,因此块、语句、表达式以及块本身都可以递归地相互嵌套到任意深度。
注意
我们为表达式的操作数命名以便讨论它们,但这些名称不稳定且可能会更改。
表达式优先级
Rust 运算符和表达式的优先级顺序如下,从强到弱排列。同一优先级的二元运算符按其结合性给定的顺序分组。
| 运算符/表达式 | 结合性 |
|---|---|
| 路径 | |
| 方法调用 | |
| 字段表达式 | 左结合 |
| 函数调用,数组索引 | |
? | |
一元运算符 - * ! & &mut | |
as | 左结合 |
* / % | 左结合 |
+ - | 左结合 |
<< >> | 左结合 |
& | 左结合 |
^ | 左结合 |
| | 左结合 |
== != < > <= >= | 需要括号 |
&& | 左结合 |
|| | 左结合 |
.. ..= | 需要括号 |
= += -= *= /= %= &= |= ^= <<= >>= | 右结合 |
return break 闭包 |
操作数的求值顺序
以下表达式列表都以相同的方式求值其操作数,具体描述在列表之后。其他表达式要么不接受操作数,要么根据其各自页面上的描述有条件地求值。
- 解引用表达式
- 错误传播表达式
- 否定表达式
- 算术和逻辑二元运算符
- 比较运算符
- 类型转换表达式
- 分组表达式
- 数组表达式
- Await 表达式
- 索引表达式
- 元组表达式
- 元组索引表达式
- 结构体表达式
- 调用表达式
- 方法调用表达式
- 字段表达式
- Break 表达式
- 范围表达式
- Return 表达式
这些表达式的操作数在应用表达式的效果之前进行求值。接受多个操作数的表达式按照源代码中书写的顺序从左到右求值。
注意
哪些子表达式是表达式的操作数取决于上一节所述的表达式优先级。
例如,两个 next 方法调用将总是按相同的顺序被调用
#![allow(unused)] fn main() { // Using vec instead of array to avoid references // since there is no stable owned array iterator // at the time this example was written. let mut one_two = vec![1, 2].into_iter(); assert_eq!( (1, 2), (one_two.next().unwrap(), one_two.next().unwrap()) ); }
注意
由于这是递归应用的,这些表达式也是从最内层到最外层求值,忽略同级表达式直到没有更内层的子表达式。
Place 表达式与 Value 表达式
表达式主要分为两类:place 表达式和 value 表达式;还有第三类,较小的类别,称为赋值目标表达式。在每个表达式内部,操作数也可能出现在 place 上下文或 value 上下文中。表达式的求值取决于其自身的类别以及其所在的上下文。
Place 表达式是表示内存位置的表达式。
这些表达式包括指向本地变量的路径、静态变量、解引用(*expr)、数组索引表达式(expr[expr])、字段引用(expr.f)以及用括号括起来的 place 表达式。
所有其他表达式都是 value 表达式。
Value 表达式是表示实际值的表达式。
以下上下文是 place 表达式上下文:
- 复合赋值表达式的左操作数。
- 一元借用、裸借用或解引用运算符的操作数。
- 字段表达式的操作数。
- 数组索引表达式中被索引的操作数。
- 任何隐式借用的操作数。
- let 语句的初始化式。
- 待匹配值 (scrutinee) 在
if let、match或while let表达式中。 - 函数式更新结构体表达式的基表达式。
注意
从历史上看,place 表达式曾被称为 左值 (lvalues),而 value 表达式曾被称为 右值 (rvalues)。
赋值目标表达式是在赋值表达式左操作数中出现的表达式。具体来说,赋值目标表达式包括:
在赋值目标表达式内部允许任意使用括号。
被移动和被复制的类型
当 place 表达式在 value 表达式上下文或在模式中按值绑定时被求值,它表示该内存位置中持有的值。
如果该值的类型实现了 Copy 特性,则该值将被复制。
在其余情况下,如果该类型是 Sized,则可能可以移动该值。
只有以下 place 表达式可以被移出:
从求值为本地变量的 place 表达式中移出后,该位置变为未初始化状态,直到重新初始化之前不能再次读取。
在所有其他情况下,尝试在 value 表达式上下文中使用 place 表达式是错误的。
可变性
要使一个 place 表达式可以被赋值、可变地借用、隐式可变地借用,或者绑定到包含 ref mut 的模式,它必须是可变的。我们称这些为可变 place 表达式。相反,其他 place 表达式称为不可变 place 表达式。
以下表达式可以是可变 place 表达式上下文:
- 当前未被借用的可变变量。
- 可变的
static项. - 临时值.
- 字段:这会在可变 place 表达式上下文中求值其子表达式。
*mut T指针的解引用。- 类型为
&mut T的变量或变量字段的解引用。注意:这是下一条规则要求的例外情况。 - 实现了
DerefMut特性的类型的解引用:这要求被解引用的值在可变 place 表达式上下文中求值。 - 实现了
IndexMut特性的类型的数组索引:这会在可变 place 表达式上下文中求值被索引的值(而非索引本身)。
临时值
在大多数 place 表达式上下文中,当使用 value 表达式时,会创建一个临时的无名内存位置并用该值进行初始化。表达式求值的结果就是该位置,除非它被提升为 static。临时值的丢弃范围 (drop scope) 通常是包围它的语句的末尾。
隐式借用
某些表达式会通过隐式借用,将一个表达式视为 place 表达式。例如,可以直接比较两个未定大小的切片是否相等,因为 == 运算符会隐式借用其操作数。
#![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; // Equivalent form: ::std::cmp::PartialEq::eq(&*a, &*b); }
隐式借用可能发生在以下表达式中:
重载 Trait
以下许多运算符和表达式可以使用 std::ops 或 std::cmp 中的 trait 为其他类型重载。这些 trait 也以相同的名称存在于 core::ops 和 core::cmp 中。
表达式属性
表达式之前的外部属性只允许在少数特定情况下使用:
在以下表达式之前永远不允许使用: