块表达式
Syntax
BlockExpression →
{
InnerAttribute*
Statements?
}
BlockExpressionNoInnerAttributes →
{
Statements?
}
Statements →
Statement+
| Statement+ ExpressionWithoutBlock
| ExpressionWithoutBlock
块表达式(或称块)是一种控制流表达式,也是项和变量声明的匿名命名空间作用域。
作为控制流表达式,块按顺序执行其组成的非项声明语句,然后是其可选的最终表达式。
作为匿名命名空间作用域,项声明仅在块自身内部有效,由 let 语句声明的变量从下一条语句开始直到块结束都有效。更多细节参见作用域章节。
块的语法是 {,然后是一些内部属性,然后是一些语句,然后是一个可选的表达式(称为最终操作数),最后是 }。
语句通常要求后跟分号,有两个例外:
- 项声明语句不需要后跟分号。
- 表达式语句通常要求后跟分号,除非其外围表达式是控制流表达式。
此外,语句之间允许额外的分号,但这些分号不影响语义。
在求值块表达式时,每条语句(除了项声明语句)按顺序执行。
然后执行最终操作数(如果有给出的话)。
当块包含最终操作数时,块具有该最终操作数的类型和值。
#![allow(unused)]
fn main() {
let x: u8 = { 0u8 }; // `0u8` 是最终操作数。
assert_eq!(x, 0);
let x: u8 = { (); 0u8 }; // 同上。
assert_eq!(x, 0);
}
#![allow(unused)]
fn main() {
let x: () = {}; // 没有最终操作数。
assert_eq!(x, ());
let x: () = { 0u8; }; // 同上。
assert_eq!(x, ());
}
当块不包含最终操作数且块发散时,块具有永不类型且没有最终值(因为其类型是无人居住的)。
#![allow(unused)]
fn main() {
fn f() -> ! { loop {}; } // 发散且没有最终操作数。
// ^^^^^^^^^^^^
// 函数体是块表达式。
}
Note
注意,块没有最终操作数与有显式的单元类型最终操作数是不同的。例如,即使此块发散,块的类型也是单元而非永不。
#![allow(unused)] fn main() { fn f() -> ! { loop {}; () } // 错误:类型不匹配。 // ^^^^^^^^^^^^^^^ 此块具有单元类型。 }
Note
作为控制流表达式,如果块表达式是表达式语句的外围表达式,则期望类型为
(),除非其后紧跟分号。
如果一个块的所有可达控制流路径都包含一个发散表达式,则该块被认为是发散的,除非该表达式是未被读取的位置表达式。
#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn no_control_flow() -> ! {
// 没有条件语句,因此整个函数体是发散的。
loop {}
}
fn control_flow_diverging() -> ! {
// 所有路径都是发散的,因此整个函数体是发散的。
if true {
loop {}
} else {
loop {}
}
}
fn control_flow_not_diverging() -> () {
// 某些路径不是发散的,因此整个块不是发散的。
if true {
()
} else {
loop {}
}
}
// 注意:这里使用了不稳定的 never 类型,该类型仅在
// Rust 的 nightly 通道上可用。这只是为了说明目的。
// 在稳定版 Rust 中也可能遇到这种情况,但需要更
// 复杂的示例。
struct Foo {
x: !,
}
fn make<T>() -> T { loop {} }
fn diverging_place_read() -> ! {
let foo = Foo { x: make() };
// 读取位置表达式产生一个发散块。
let _x = foo.x;
}
}
#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn make<T>() -> T { loop {} }
struct Foo {
x: !,
}
fn diverging_place_not_read() -> ! {
let foo = Foo { x: make() };
// 赋值给 `_` 意味着该位置未被读取。
let _ = foo.x;
} // 错误:类型不匹配。
}
块始终是值表达式,并在值表达式上下文中求值最后一个操作数。
Note
这可以用于在确实需要时强制移动值。例如,下面的示例在调用
consume_self时失败,因为结构体已在块表达式中从s移出。#![allow(unused)] fn main() { struct Struct; impl Struct { fn consume_self(self) {} fn borrow_self(&self) {} } fn move_by_block_expression() { let s = Struct; // 在块表达式中将值从 `s` 移出。 (&{ s }).borrow_self(); // 因为 `s` 已被移出,所以执行失败。 s.consume_self(); } }
async 块
Syntax
AsyncBlockExpression → async move? BlockExpression
async 块是块表达式的一种变体,它求值为一个 future。
块的最终表达式(如果有的话)决定 future 的结果值。
执行 async 块类似于执行闭包表达式:其直接效果是产生并返回一个匿名类型。
然而,闭包返回一个实现 std::ops::Fn trait 族之一的类型,而 async 块返回的类型实现 std::future::Future trait。
此类型的具体数据格式未作规定。
Note
rustc 生成的 future 类型大致等价于一个枚举,每个
await点对应一个变体,每个变体存储从其对应点恢复所需的数据。
2018 Edition differences
Async 块仅从 Rust 2018 起可用。
捕获模式
Async 块使用与闭包相同的捕获模式从环境中捕获变量。与闭包类似,当写为 async { .. } 时,每个变量的捕获模式将根据块的内容推断。而 async move { .. } 块则会将所有引用的变量移动到生成的 future 中。
异步上下文
因为 async 块构造一个 future,它们定义了一个异步上下文,其中又可以包含 await 表达式。异步上下文由 async 块以及异步函数体建立,异步函数的语义是通过 async 块来定义的。
控制流运算符
Async 块像函数边界一样运作,很像闭包。
因此,? 运算符和 return 表达式都影响 future 的输出,而不是外围函数或其他上下文。也就是说,async 块内的 return <expr> 将返回 <expr> 的结果作为 future 的输出。类似地,如果 <expr>? 传播错误,该错误将作为 future 的结果传播。
最后,break 和 continue 关键字不能用于从 async 块中跳出。因此以下代码是非法的:
#![allow(unused)]
fn main() {
loop {
async move {
break; // 错误[E0267]:`async` 块内的 `break`
}
}
}
const 块
Syntax
ConstBlockExpression → const BlockExpression
const 块是块表达式的一种变体,其主体在编译时求值而不是在运行时求值。
Const 块允许你定义常量值而无需定义新的常量项,因此有时也称为内联常量。它还支持类型推断,因此无需像常量项那样指定类型。
与自由项常量项不同,Const 块能够引用作用域内的泛型参数。它们被脱糖为作用域内有泛型参数的常量项(类似于关联常量,但没有与之关联的 trait 或类型)。例如,以下代码:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
const { std::mem::size_of::<T>() + 1 }
}
}
等价于:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
{
struct Const<T>(T);
impl<T> Const<T> {
const CONST: usize = std::mem::size_of::<T>() + 1;
}
Const::<T>::CONST
}
}
}
如果 const 块表达式在运行时被执行,则常量保证被求值,即使其返回值被忽略:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
// 如果此代码曾经被执行,则断言肯定已在编译时被求值。
const { assert!(std::mem::size_of::<T>() > 0); }
// 此处我们可以有依赖于类型非零大小的 unsafe 代码。
/* ... */
42
}
}
如果 const 块表达式不在运行时被执行,它可能被求值也可能不被求值:
#![allow(unused)]
fn main() {
if false {
// 当程序构建时,panic 可能发生也可能不发生。
const { panic!(); }
}
}
unsafe 块
Syntax
UnsafeBlockExpression → unsafe BlockExpression
有关何时使用 unsafe 的更多信息,请参见 unsafe 块。
代码块可以用 unsafe 关键字作为前缀,以允许不安全操作。示例:
#![allow(unused)]
fn main() {
unsafe {
let b = [13u8, 17u8];
let a = &b[0] as *const u8;
assert_eq!(*a, 13);
assert_eq!(*a.offset(1), 17);
}
unsafe fn an_unsafe_fn() -> i32 { 10 }
let a = unsafe { an_unsafe_fn() };
}
带标签的块表达式
带标签的块表达式在循环和其他可中断表达式一节中描述。
块表达式上的属性
在以下情况下,允许在块表达式的开花括号后直接放置内部属性:
- 函数和方法体。
- 循环体(
loop、while和for)。 - 用作语句的块表达式。
- 作为数组表达式、元组表达式、调用表达式和元组式结构体表达式元素的块表达式。
- 作为另一个块表达式的尾部表达式的块表达式。
在块表达式上有意义的属性是 cfg 和 lint 检查属性。
例如,此函数在 unix 平台上返回 true,在其他平台上返回 false。
#![allow(unused)]
fn main() {
fn is_unix_platform() -> bool {
#[cfg(unix)] { true }
#[cfg(not(unix))] { false }
}
}