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

块表达式

Syntax
BlockExpression
    {
        InnerAttribute*
        Statements?
    }

BlockExpressionNoInnerAttributes
    {
        Statements?
    }

Statements
      Statement+
    | Statement+ ExpressionWithoutBlock
    | ExpressionWithoutBlock

块表达式(或称)是一种控制流表达式,也是项和变量声明的匿名命名空间作用域。

作为控制流表达式,块按顺序执行其组成的非项声明语句,然后是其可选的最终表达式。

作为匿名命名空间作用域,项声明仅在块自身内部有效,由 let 语句声明的变量从下一条语句开始直到块结束都有效。更多细节参见作用域章节。

块的语法是 {,然后是一些内部属性,然后是一些语句,然后是一个可选的表达式(称为最终操作数),最后是 }

语句通常要求后跟分号,有两个例外:

  1. 项声明语句不需要后跟分号。
  2. 表达式语句通常要求后跟分号,除非其外围表达式是控制流表达式。

此外,语句之间允许额外的分号,但这些分号不影响语义。

在求值块表达式时,每条语句(除了项声明语句)按顺序执行。

然后执行最终操作数(如果有给出的话)。

当块包含最终操作数时,块具有该最终操作数的类型和值。

#![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
AsyncBlockExpressionasync 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 的结果传播。

最后,breakcontinue 关键字不能用于从 async 块中跳出。因此以下代码是非法的:

#![allow(unused)]
fn main() {
loop {
    async move {
        break; // 错误[E0267]:`async` 块内的 `break`
    }
}
}

const

Syntax
ConstBlockExpressionconst 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
UnsafeBlockExpressionunsafe 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() };
}

带标签的块表达式

带标签的块表达式在循环和其他可中断表达式一节中描述。

块表达式上的属性

在以下情况下,允许在块表达式的开花括号后直接放置内部属性

在块表达式上有意义的属性是 cfglint 检查属性

例如,此函数在 unix 平台上返回 true,在其他平台上返回 false

#![allow(unused)]
fn main() {
fn is_unix_platform() -> bool {
    #[cfg(unix)] { true }
    #[cfg(not(unix))] { false }
}
}