表达式
语法
表达式 :
无块表达式
| 带块表达式无块表达式 :
外部属性*†
(
字面量表达式
| 路径表达式
| 运算符表达式
| 分组表达式
| 数组表达式
| Await 表达式
| 索引表达式
| 元组表达式
| 元组索引表达式
| 结构体表达式
| 调用表达式
| 方法调用表达式
| 字段表达式
| 闭包表达式
| 异步块表达式
| Continue 表达式
| Break 表达式
| 范围表达式
| Return 表达式
| 下划线表达式
| 宏调用
)带块表达式 :
外部属性*†
(
块表达式
| 常量块表达式
| 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()) ); }
注意:由于这是递归应用的,这些表达式也是从最内层到最外层进行计算的,忽略兄弟表达式,直到没有内部子表达式为止。
位置表达式和值表达式
表达式分为两大类:位置表达式和值表达式;还有一个第三类较小的表达式,称为赋值表达式。在每个表达式中,操作数也可能出现在位置上下文或值上下文中。表达式的求值取决于它自身的类别和它出现的上下文。
一个位置表达式是一个表示内存位置的表达式。这些表达式是路径,它们引用局部变量、静态变量、解引用(*expr
)、数组索引表达式 (expr[expr]
)、字段引用 (expr.f
) 和带括号的位置表达式。所有其他表达式都是值表达式。
一个值表达式是一个表示实际值的表达式。
以下上下文是位置表达式上下文
- 复合赋值表达式的左操作数。
- 一元借用、原始借用或解引用运算符的操作数。
- 字段表达式的操作数。
- 数组索引表达式的索引操作数。
- 任何隐式借用的操作数。
- let 语句的初始化器。
- 检查对象的
if let
、match
或while let
表达式。 - 函数式更新结构体表达式的基础。
注意:历史上,位置表达式被称为 左值,值表达式被称为 右值。
一个赋值表达式是出现在赋值表达式的左操作数中的表达式。明确地说,赋值表达式是
允许在赋值表达式内部进行任意的括号化。
移动和复制类型
当在值表达式上下文中计算位置表达式,或者在模式中按值绑定时,它表示该内存位置中保存的值。如果该值的类型实现了 Copy
,则该值将被复制。在其余情况下,如果该类型是Sized
,则可以移动该值。只有以下位置表达式可以被移出
在移出计算结果为局部变量的位置表达式后,该位置被取消初始化,并且在重新初始化之前无法再次从中读取。在所有其他情况下,尝试在值表达式上下文中使用位置表达式都是错误的。
可变性
为了使位置表达式可以赋值、可变借用、隐式可变借用或绑定到包含 ref mut
的模式,它必须是可变的。我们称这些为可变位置表达式。相比之下,其他位置表达式称为不可变位置表达式。
以下表达式可以是可变位置表达式上下文
- 当前未被借用的可变变量。
- 可变的
static
项. - 临时值.
- 字段:这会在可变位置表达式上下文中计算子表达式。
*mut T
指针的解引用。- 类型为
&mut T
的变量或变量字段的解引用。注意:这是下一条规则的例外。 - 实现
DerefMut
的类型的解引用:这需要解引用的值在可变位置表达式上下文中计算。 - 实现
IndexMut
的类型的数组索引:这会在可变位置表达式上下文中计算被索引的值,但不计算索引。
临时变量
在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名内存位置并将其初始化为该值。表达式改为计算为该位置,除非提升为 static
。临时的drop 作用域通常是封闭语句的结尾。
隐式借用
某些表达式会通过隐式借用将表达式视为位置表达式。例如,可以直接比较两个无大小的切片是否相等,因为 ==
运算符会隐式借用其操作数
#![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); }
隐式借用可以在以下表达式中进行
重载特性
以下许多运算符和表达式也可以使用 std::ops
或 std::cmp
中的特性为其他类型重载。这些特性也以相同的名称存在于 core::ops
和 core::cmp
中。
表达式属性
表达式之前的外部属性仅在少数特定情况下允许。
它们永远不允许出现在以下情况之前: