Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

表达式

表达式(Expression)可以有两个角色:它总是产生一个,并且可能有效果(也称为“副作用“)。

表达式求值为一个值,并在求值期间产生效果。

许多表达式包含子表达式,称为表达式的操作数

每种表达式的含义决定了几件事:

  • 求值表达式时是否求值操作数
  • 求值操作数的顺序
  • 如何组合操作数的值以获得表达式的值

通过这种方式,表达式的结构决定了执行的结构。块只是另一种表达式,因此块、语句、表达式和块可以递归地嵌套在彼此内部,达到任意深度。

Note

我们给表达式的操作数命名以便讨论,但这些名称不稳定,可能会更改。

表达式优先级

Rust 运算符和表达式的优先级按以下顺序排列,从强到弱。相同优先级的二元运算符按其关联性分组。

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

操作数的求值顺序

以下表达式列表都以相同的方式求值其操作数,如列表之后所述。其他表达式要么不接受操作数,要么按其各自页面中描述的条件求值。

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

这些表达式的操作数在应用表达式的效果之前求值。接受多个操作数的表达式按源代码中编写的顺序从左到右求值。

Note

哪些子表达式是表达式的操作数由上一节的表达式优先级确定。

例如,两个 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())
);
}

Note

由于这是递归应用的,这些表达式也从最内层到最外层求值,忽略兄弟节点直到没有内部子表达式。

位置表达式和值表达式

表达式分为两个主要类别:位置表达式和值表达式;还有第三个次要类别的表达式称为赋值表达式。在每个表达式内,操作数同样可能出现在位置上下文或值上下文中。表达式的求值取决于其自身的类别和它出现的上下文。

位置表达式是表示内存位置的表达式。

这些表达式是引用局部变量的路径静态变量解引用*expr)、数组索引表达式(expr[expr])、字段引用(expr.f)和括号位置表达式。

所有其他表达式都是值表达式。

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

以下是位置表达式上下文:

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("") }
// Due to the call being an extending expression and the argument
// being a super operand, the inner block is an extending expression,
// so the scope of the temporary created in its trailing expression
// is extended.
let _ = format_args!("{}", { &temp() }); // OK
}

format_args! 的超级操作数被隐式借用,因此是位置表达式上下文。当值表达式作为参数传递时,它会创建一个超级临时值

#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
let x = format_args!("{}", temp());
x; // <-- The temporary is extended, allowing use here.
}

调用 format_args! 的展开有时会创建其他内部超级临时值

#![allow(unused)]
fn main() {
let x = {
    // This call creates an internal temporary.
    let x = format_args!("{:?}", 0);
    x // <-- The temporary is extended, allowing its use here.
}; // <-- The temporary is dropped here.
x; // ERROR
}
#![allow(unused)]
fn main() {
// This call doesn't create an internal temporary.
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() {}
// As above for `format_args!`.
let _ = pin!({ &temp() }); // OK
}

pin! 的参数是值表达式上下文,并创建一个超级临时值

#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// The argument is evaluated into a super temporary.
let x = pin!(temp());
// The temporary is extended, allowing its use here.
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;
// Equivalent form:
::std::cmp::PartialEq::eq(&*a, &*b);
}

可以在以下表达式中进行隐式借用:

重载 trait

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

表达式属性

表达式之前的外部属性仅在少数特定情况下允许:

它们在以下情况之前永远不允许: