From 24a2ca352b6bffc4389c676eafbb20a39d3b7c8e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 15 Feb 2026 14:00:17 -0800 Subject: [PATCH 1/3] block-expr: add new rule expr.block.result-value Document that the value of a block expression is based on the final operand (if present), just like the type. --- src/expressions/block-expr.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index ef5fbe1c32..f0fbd9758d 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -45,6 +45,9 @@ Then the final operand is executed, if given. r[expr.block.type] The type of a block is the type of its final operand; if that operand is omitted, the type is the [unit type], unless the block [diverges][expr.block.diverging], in which case it is the [never type]. +r[expr.block.result-value] +The value of a block is the value of its final operand; if that operand is omitted, the value is the [unit value][type.tuple.unit], unless the block [diverges][expr.block.diverging], in which case the value is unused. + ```rust # fn fn_call() {} let _: () = { From fc52d08aae57b616914fe8db47493837bd338f03 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 17 Feb 2026 18:58:43 +0000 Subject: [PATCH 2/3] Revise text for `expr.block.result-value` When there is no final operand to a block (i.e. nothing or nothing after the final semicolon) and the block diverges, we need to say that there is no value as the type is uninhabited. Let's say that. --- src/expressions/block-expr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index f0fbd9758d..3d0c960f6b 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -46,7 +46,7 @@ r[expr.block.type] The type of a block is the type of its final operand; if that operand is omitted, the type is the [unit type], unless the block [diverges][expr.block.diverging], in which case it is the [never type]. r[expr.block.result-value] -The value of a block is the value of its final operand; if that operand is omitted, the value is the [unit value][type.tuple.unit], unless the block [diverges][expr.block.diverging], in which case the value is unused. +The value of a block is the value of its final operand; if that operand is omitted, the value is the [unit value][type.tuple.unit], unless the block [diverges][expr.block.diverging], in which case the block has no value as its type is uninhabited. ```rust # fn fn_call() {} From ca60dddb97eac82c93b44aabc902ebc641cff337 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 17 Feb 2026 20:20:13 +0000 Subject: [PATCH 3/3] Revise trailing expression block rules An earlier commit added a single rule, `r[expr.block.result-value]`, that documented the value of a block expression in one compound sentence. While correct, that sentence carried a lot of weight -- the three cases it described (final operand present, final operand absent, and diverging) are different enough that each deserves its own rule and example. This commit replaces `r[expr.block.result-value]` and `r[expr.block.type]` with three rules that each describe the type and value together for one case: - `r[expr.block.value-trailing-expr]`: when the block has a final operand, it has that operand's type and value. - `r[expr.block.value-no-trailing-expr]`: when the block has no final operand and doesn't diverge, it has unit type and unit value. - `r[expr.block.value-diverges-no-trailing-expr]`: when the block has no final operand and diverges, it has the never type and has no final value (because its type is uninhabited). The existing example demonstrated type and value together in one block. We replace it with focused examples for each rule, each demonstrating the specific case that rule describes. A note after the third rule highlights the distinction between omitting the final operand and having an explicit unit final operand -- the diverging case makes this difference visible, since `{ loop {}; }` has never type while `{ loop {}; () }` has unit type. --- src/expressions/block-expr.md | 49 +++++++++++++++++++++++++---------- src/glossary.md | 1 + 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 3d0c960f6b..4ae74d1674 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -42,26 +42,43 @@ When evaluating a block expression, each statement, except for item declaration r[expr.block.result] Then the final operand is executed, if given. -r[expr.block.type] -The type of a block is the type of its final operand; if that operand is omitted, the type is the [unit type], unless the block [diverges][expr.block.diverging], in which case it is the [never type]. +r[expr.block.value-trailing-expr] +When a block contains a [final operand], the block has the type and value of that final operand. -r[expr.block.result-value] -The value of a block is the value of its final operand; if that operand is omitted, the value is the [unit value][type.tuple.unit], unless the block [diverges][expr.block.diverging], in which case the block has no value as its type is uninhabited. +```rust +let x: u8 = { 0u8 }; // `0u8` is the final operand. +assert_eq!(x, 0); +let x: u8 = { (); 0u8 }; // As above. +assert_eq!(x, 0); +``` + +r[expr.block.value-no-trailing-expr] +When a block does not contain a [final operand] and the block does not diverge, the block has [unit type] and [unit value]. ```rust -# fn fn_call() {} -let _: () = { - fn_call(); -}; +let x: () = {}; // Has no final operand. +assert_eq!(x, ()); +let x: () = { 0u8; }; // As above. +assert_eq!(x, ()); +``` -let five: i32 = { - fn_call(); - 5 -}; +r[expr.block.value-diverges-no-trailing-expr] +When a block does not contain a [final operand] and the block [diverges], the block has the [never type] and has no final value (because its type is [uninhabited]). -assert_eq!(5, five); +```rust,no_run +fn f() -> ! { loop {}; } // Diverges and has no final operand. +// ^^^^^^^^^^^^ +// The body of a function is a block expression. ``` +> [!NOTE] +> Observe that a block having no final operand is distinct from having an explicit final operand with unit type. E.g., even though this block diverges, the type of the block is [unit] rather than [never]. +> +> ```rust,compile_fail,E0308 +> fn f() -> ! { loop {}; () } // ERROR: Mismatched types. +> // ^^^^^^^^^^^^^^^ This block has unit type. +> ``` + > [!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. @@ -335,12 +352,15 @@ fn is_unix_platform() -> bool { [call expressions]: call-expr.md [capture modes]: ../types/closure.md#capture-modes [constant items]: ../items/constant-items.md +[diverges]: expr.block.diverging +[final operand]: expr.block.inner-attributes [free item]: ../glossary.md#free-item [function]: ../items/functions.md [inner attributes]: ../attributes.md [method]: ../items/associated-items.md#methods [mutable reference]: ../types/pointer.md#mutables-references- [never type]: type.never +[never]: type.never [place expression]: expr.place-value.place-memory-location [scopes]: ../names/scopes.md [shared references]: ../types/pointer.md#shared-references- @@ -349,7 +369,10 @@ fn is_unix_platform() -> bool { [struct]: struct-expr.md [the lint check attributes]: ../attributes/diagnostics.md#lint-check-attributes [tuple expressions]: tuple-expr.md +[uninhabited]: glossary.uninhabited [unit type]: type.tuple.unit +[unit value]: type.tuple.unit +[unit]: type.tuple.unit [unsafe operations]: ../unsafety.md [value expressions]: ../expressions.md#place-expressions-and-value-expressions [Loops and other breakable expressions]: expr.loop.block-labels diff --git a/src/glossary.md b/src/glossary.md index 72bb1431a9..9d39173e01 100644 --- a/src/glossary.md +++ b/src/glossary.md @@ -206,6 +206,7 @@ A type which does not appear as an argument to another type. For example, `T` is Compile-time or run-time behavior that is not specified. This may result in, but is not limited to: process termination or corruption; improper, incorrect, or unintended computation; or platform-specific results. [More][undefined-behavior]. +r[glossary.uninhabited] ### Uninhabited A type is uninhabited if it has no constructors and therefore can never be instantiated. An uninhabited type is "empty" in the sense that there are no values of the type. The canonical example of an uninhabited type is the [never type] `!`, or an enum with no variants `enum Never { }`. Opposite of [Inhabited](#inhabited).