Block expressions

Syntax
BlockExpression :
   {
      InnerAttribute*
      Statements?
   }

Statements :
      Statement+
   | Statement+ ExpressionWithoutBlock
   | ExpressionWithoutBlock

A block expression, or block, is a control flow expression and anonymous namespace scope for items and variable declarations. As a control flow expression, a block sequentially executes its component non-item declaration statements and then its final optional expression. As an anonymous namespace scope, item declarations are only in scope inside the block itself and variables declared by let statements are in scope from the next statement until the end of the block. See the scopes chapter for more details.

The syntax for a block is {, then any inner attributes, then any number of statements, then an optional expression, called the final operand, and finally a }.

Statements are usually required to be followed by a semicolon, with two exceptions:

  1. Item declaration statements do not need to be followed by a semicolon.
  2. Expression statements usually require a following semicolon except if its outer expression is a flow control expression.

Furthermore, extra semicolons between statements are allowed, but these semicolons do not affect semantics.

When evaluating a block expression, each statement, except for item declaration statements, is executed sequentially. Then the final operand is executed, if given.

The type of a block is the type of the final operand, or () if the final operand is omitted.

#![allow(unused)]
fn main() {
fn fn_call() {}
let _: () = {
    fn_call();
};

let five: i32 = {
    fn_call();
    5
};

assert_eq!(5, five);
}

Note: As a control flow expression, if a block expression is the outer expression of an expression statement, the expected type is () unless it is followed immediately by a semicolon.

Blocks are always value expressions and evaluate the last operand in value expression context.

Note: This can be used to force moving a value if really needed. For example, the following example fails on the call to consume_self because the struct was moved out of s in the block expression.

#![allow(unused)]
fn main() {
struct Struct;

impl Struct {
    fn consume_self(self) {}
    fn borrow_self(&self) {}
}

fn move_by_block_expression() {
    let s = Struct;

    // Move the value out of `s` in the block expression.
    (&{ s }).borrow_self();

    // Fails to execute because `s` is moved out of.
    s.consume_self();
}
}

async blocks

Syntax
AsyncBlockExpression :
   async move? BlockExpression

An async block is a variant of a block expression which evaluates to a future. The final expression of the block, if present, determines the result value of the future.

Executing an async block is similar to executing a closure expression: its immediate effect is to produce and return an anonymous type. Whereas closures return a type that implements one or more of the std::ops::Fn traits, however, the type returned for an async block implements the std::future::Future trait. The actual data format for this type is unspecified.

Note: The future type that rustc generates is roughly equivalent to an enum with one variant per await point, where each variant stores the data needed to resume from its corresponding point.

Edition differences: Async blocks are only available beginning with Rust 2018.

Capture modes

Async blocks capture variables from their environment using the same capture modes as closures. Like closures, when written async { .. } the capture mode for each variable will be inferred from the content of the block. async move { .. } blocks however will move all referenced variables into the resulting future.

Async context

Because async blocks construct a future, they define an async context which can in turn contain await expressions. Async contexts are established by async blocks as well as the bodies of async functions, whose semantics are defined in terms of async blocks.

Control-flow operators

Async blocks act like a function boundary, much like closures. Therefore, the ? operator and return expressions both affect the output of the future, not the enclosing function or other context. That is, return <expr> from within an async block will return the result of <expr> as the output of the future. Similarly, if <expr>? propagates an error, that error is propagated as the result of the future.

Finally, the break and continue keywords cannot be used to branch out from an async block. Therefore the following is illegal:

#![allow(unused)]
fn main() {
loop {
    async move {
        break; // error[E0267]: `break` inside of an `async` block
    }
}
}

const blocks

Syntax
ConstBlockExpression :
   const BlockExpression

A const block is a variant of a block expression whose body evaluates at compile-time instead of at runtime.

Const blocks allows you to define a constant value without having to define new constant items, and thus they are also sometimes referred as inline consts. It also supports type inference so there is no need to specify the type, unlike constant items.

Const blocks have the ability to reference generic parameters in scope, unlike free constant items. They are desugared to constant items with generic parameters in scope (similar to associated constants, but without a trait or type they are associated with). For example, this code:

#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
    const { std::mem::size_of::<T>() + 1 }
}
}

is equivalent to:

#![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
    }
}
}

If the const block expression is executed at runtime, then the constant is guaranteed to be evaluated, even if its return value is ignored:

#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
    // If this code ever gets executed, then the assertion has definitely
    // been evaluated at compile-time.
    const { assert!(std::mem::size_of::<T>() > 0); }
    // Here we can have unsafe code relying on the type being non-zero-sized.
    /* ... */
    42
}
}

If the const block expression is not executed at runtime, it may or may not be evaluated:

#![allow(unused)]
fn main() {
if false {
    // The panic may or may not occur when the program is built.
    const { panic!(); }
}
}

unsafe blocks

Syntax
UnsafeBlockExpression :
   unsafe BlockExpression

See unsafe blocks for more information on when to use unsafe.

A block of code can be prefixed with the unsafe keyword to permit unsafe operations. Examples:

#![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() };
}

Labelled block expressions

Labelled block expressions are documented in the Loops and other breakable expressions section.

Attributes on block expressions

Inner attributes are allowed directly after the opening brace of a block expression in the following situations:

The attributes that have meaning on a block expression are cfg and the lint check attributes.

For example, this function returns true on unix platforms and false on other platforms.

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