表达式
Syntax
Expression →
ExpressionWithoutBlock
| ExpressionWithBlock
ExpressionWithoutBlock →
OuterAttribute* ExpressionWithoutBlockNoAttrs
ExpressionWithoutBlockNoAttrs →
LiteralExpression
| PathExpression
| OperatorExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| StructExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| RangeExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
ExpressionWithBlock →
OuterAttribute* ExpressionWithBlockNoAttrs
ExpressionWithBlockNoAttrs →
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
表达式可以有两种角色:它总是产生一个值,并且可能具有效果(也称为“副作用“)。
表达式求值为一个值,并在求值期间产生效果。
许多表达式包含子表达式,称为表达式的操作数。
每种表达式的含义决定了以下几件事:
- 在求值表达式时是否要求值其操作数
- 求值操作数的顺序
- 如何组合操作数的值以获得表达式的值
这样,表达式的结构就决定了执行的结构。块只是另一种表达式,因此块、语句、表达式和块可以相互递归嵌套,达到任意深度。
Note
我们为表达式的操作数命名以便讨论,但这些名称并不稳定,可能会发生变化。
表达式优先级
Rust 运算符和表达式的优先级按从强到弱的顺序排列如下。处于同一优先级的二元运算符按其结合性给定的顺序分组。
| 运算符/表达式 | 结合性 |
|---|---|
| 路径 | |
| 方法调用 | |
| 字段表达式 | 从左到右 |
| 函数调用、数组索引 | |
? | |
一元 - ! * 借用 | |
as | 从左到右 |
* / % | 从左到右 |
+ - | 从左到右 |
<< >> | 从左到右 |
& | 从左到右 |
^ | 从左到右 |
| | 从左到右 |
== != < > <= >= | 需要括号 |
&& | 从左到右 |
|| | 从左到右 |
.. ..= | 需要括号 |
= += -= *= /= %= &= |= ^= <<= >>= | 从右到左 |
return break 闭包 |
操作数的求值顺序
以下列表中的表达式都以相同的方式求值其操作数,如下文所述。其他表达式要么不接收操作数,要么根据其各自页面的描述有条件地求值。
- 解引用表达式
- 错误传播表达式
- 取反表达式
- 算术和逻辑二元运算符
- 比较运算符
- 类型转换表达式
- 分组表达式
- 数组表达式
- Await 表达式
- 索引表达式
- 元组表达式
- 元组索引表达式
- 结构体表达式
- 调用表达式
- 方法调用表达式
- 字段表达式
- Break 表达式
- 区间表达式
- Return 表达式
这些表达式的操作数在应用表达式效果之前被求值。接受多个操作数的表达式按源代码中从左到右的顺序求值。
Note
哪些子表达式是表达式的操作数由前一节中的表达式优先级决定。
例如,两个 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())
);
}
Note
由于这是递归应用的,这些表达式也从最内层到最外层求值,忽略兄弟节点,直到没有内部子表达式为止。
位置表达式和值表达式
表达式分为两大类:位置表达式和值表达式;还有第三类次要的表达式,称为赋值目标表达式。在每个表达式内部,操作数同样可以出现在位置上下文或值上下文中。表达式的求值取决于其自身的类别以及它所处的上下文。
位置表达式是表示内存位置的表达式。
这些表达式包括引用局部变量的路径、静态变量、解引用(*expr)、数组索引表达式(expr[expr])、字段引用(expr.f)和带括号的位置表达式。
所有其他表达式都是值表达式。
值表达式是表示实际值的表达式。
以下上下文是位置表达式上下文:
- 复合赋值表达式的左操作数。
- 一元借用、裸借用或解引用运算符的操作数。
- 字段表达式的操作数。
- 数组索引表达式的索引操作数。
- 元组索引表达式的元组操作数。
- 任何隐式借用的操作数。
- let 语句的初始化器。
if let、match或while let表达式的受检者。- 函数式更新结构体表达式的基值。
Note
历史上,位置表达式曾被称为lvalues,值表达式曾被称为rvalues。
赋值目标表达式是出现在赋值表达式左操作数中的表达式。具体来说,赋值目标表达式包括:
在赋值目标表达式内部允许任意加括号。
移动和复制类型
当位置表达式在值表达式上下文中求值,或在模式中以值绑定时,它表示该内存位置中持有的值。
如果该值的类型实现了 Copy,则该值将被复制。
在其余情况下,如果该类型是 Sized,则可能可以移动该值。
只有以下位置表达式可以被移出:
从求值为局部变量的位置表达式中移出后,该位置被反初始化,在重新初始化之前不能再被读取。
在所有其他情况下,尝试在值表达式上下文中使用位置表达式是错误的。
可变性
要使一个位置表达式能够被赋值、可变借用、隐式可变借用或绑定到包含 ref mut 的模式,它必须是可变的。我们称这些为可变位置表达式。相反,其他位置表达式称为不可变位置表达式。
以下表达式可以是可变位置表达式上下文:
- 当前未被借用的可变变量。
- 可变
static项。 - 临时值。
- 字段:这在可变位置表达式上下文中求值子表达式。
- 对
*mut T指针的解引用。 - 对类型为
&mut T的变量或变量的字段的解引用。注意:这是下一条规则要求的例外。 - 对实现了
DerefMut的类型的解引用:这要求被解引用的值在可变位置表达式上下文中求值。 - 对实现了
IndexMut的类型的数组索引:这在可变位置表达式上下文中求值被索引的值,但不求值索引。
临时值
在大多数位置表达式上下文中使用值表达式时,会创建一个临时的无名内存位置并初始化为该值。表达式求值为该位置,除非被提升为 static。临时值的丢弃作用域通常是包围语句的末尾。
超级宏
某些内置宏可能会创建临时值,其作用域可以被延长。这些临时值是超级临时值,这些宏是超级宏。这些宏的调用是超级宏调用表达式。这些宏的参数可以是超级操作数。
Note
当超级宏调用表达式是延长表达式时,其超级操作数是延长表达式,并且超级临时值的作用域被延长。参见 destructors.scope.lifetime-extension.exprs。
format_args!
除格式字符串参数外,传递给 format_args! 的所有参数都是超级操作数。
#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
// 由于该调用是延长表达式且参数是超级操作数,
// 内部块是延长表达式,因此在其尾部表达式中创建的
// 临时值的作用域被延长。
let _ = format_args!("{}", { &temp() }); // OK
}
format_args! 的超级操作数被隐式借用,因此是位置表达式上下文。当传递值表达式作为参数时,它创建一个超级临时值。
#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
let x = format_args!("{}", temp());
x; // <-- 临时值被延长了,允许在此处使用。
}
format_args! 调用的展开有时会创建其他内部的超级临时值。
#![allow(unused)]
fn main() {
let x = {
// 此调用创建一个内部临时值。
let x = format_args!("{:?}", 0);
x // <-- 临时值被延长了,允许在此处使用。
}; // <-- 临时值在此处被丢弃。
x; // 错误
}
#![allow(unused)]
fn main() {
// 此调用不创建内部临时值。
let x = { let x = format_args!("{}", 0); x };
x; // OK
}
Note
format_args!何时创建或不创建内部临时值的细节目前尚未规定。
pin!
pin! 的参数是超级操作数。
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// 与上面的 `format_args!` 相同。
let _ = pin!({ &temp() }); // OK
}
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// 参数被求值为一个超级临时值。
let x = pin!(temp());
// 临时值被延长了,允许在此处使用。
x; // OK
}
隐式借用
某些表达式会将一个表达式视为位置表达式,通过隐式借用它。例如,可以直接比较两个非固定大小的切片是否相等,因为 == 运算符会隐式借用其操作数:
#![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);
}
隐式借用可能在以下表达式中发生:
- 方法调用表达式中的左操作数。
- 字段表达式中的左操作数。
- 调用表达式中的左操作数。
- 数组索引表达式中的左操作数。
- 解引用运算符(
*)的操作数。 - 比较的操作数。
- 复合赋值的左操作数。
- 传递给
format_args!的参数(格式字符串除外)。
重载 trait
以下许多运算符和表达式也可以使用 std::ops 或 std::cmp 中的 trait 为其他类型进行重载。这些 trait 也以相同的名称存在于 core::ops 和 core::cmp 中。
表达式属性
表达式前允许外部属性的情况仅限于以下几种:
绝不允许在以下表达式之前: