Skip to content

Commit ca60ddd

Browse files
committed
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.
1 parent fc52d08 commit ca60ddd

File tree

2 files changed

+37
-13
lines changed

2 files changed

+37
-13
lines changed

src/expressions/block-expr.md

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,43 @@ When evaluating a block expression, each statement, except for item declaration
4242
r[expr.block.result]
4343
Then the final operand is executed, if given.
4444

45-
r[expr.block.type]
46-
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].
45+
r[expr.block.value-trailing-expr]
46+
When a block contains a [final operand], the block has the type and value of that final operand.
4747

48-
r[expr.block.result-value]
49-
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.
48+
```rust
49+
let x: u8 = { 0u8 }; // `0u8` is the final operand.
50+
assert_eq!(x, 0);
51+
let x: u8 = { (); 0u8 }; // As above.
52+
assert_eq!(x, 0);
53+
```
54+
55+
r[expr.block.value-no-trailing-expr]
56+
When a block does not contain a [final operand] and the block does not diverge, the block has [unit type] and [unit value].
5057

5158
```rust
52-
# fn fn_call() {}
53-
let _: () = {
54-
fn_call();
55-
};
59+
let x: () = {}; // Has no final operand.
60+
assert_eq!(x, ());
61+
let x: () = { 0u8; }; // As above.
62+
assert_eq!(x, ());
63+
```
5664

57-
let five: i32 = {
58-
fn_call();
59-
5
60-
};
65+
r[expr.block.value-diverges-no-trailing-expr]
66+
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]).
6167

62-
assert_eq!(5, five);
68+
```rust,no_run
69+
fn f() -> ! { loop {}; } // Diverges and has no final operand.
70+
// ^^^^^^^^^^^^
71+
// The body of a function is a block expression.
6372
```
6473

74+
> [!NOTE]
75+
> 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].
76+
>
77+
> ```rust,compile_fail,E0308
78+
> fn f() -> ! { loop {}; () } // ERROR: Mismatched types.
79+
> // ^^^^^^^^^^^^^^^ This block has unit type.
80+
> ```
81+
6582
> [!NOTE]
6683
> 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.
6784
@@ -335,12 +352,15 @@ fn is_unix_platform() -> bool {
335352
[call expressions]: call-expr.md
336353
[capture modes]: ../types/closure.md#capture-modes
337354
[constant items]: ../items/constant-items.md
355+
[diverges]: expr.block.diverging
356+
[final operand]: expr.block.inner-attributes
338357
[free item]: ../glossary.md#free-item
339358
[function]: ../items/functions.md
340359
[inner attributes]: ../attributes.md
341360
[method]: ../items/associated-items.md#methods
342361
[mutable reference]: ../types/pointer.md#mutables-references-
343362
[never type]: type.never
363+
[never]: type.never
344364
[place expression]: expr.place-value.place-memory-location
345365
[scopes]: ../names/scopes.md
346366
[shared references]: ../types/pointer.md#shared-references-
@@ -349,7 +369,10 @@ fn is_unix_platform() -> bool {
349369
[struct]: struct-expr.md
350370
[the lint check attributes]: ../attributes/diagnostics.md#lint-check-attributes
351371
[tuple expressions]: tuple-expr.md
372+
[uninhabited]: glossary.uninhabited
352373
[unit type]: type.tuple.unit
374+
[unit value]: type.tuple.unit
375+
[unit]: type.tuple.unit
353376
[unsafe operations]: ../unsafety.md
354377
[value expressions]: ../expressions.md#place-expressions-and-value-expressions
355378
[Loops and other breakable expressions]: expr.loop.block-labels

src/glossary.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ A type which does not appear as an argument to another type. For example, `T` is
206206

207207
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].
208208

209+
r[glossary.uninhabited]
209210
### Uninhabited
210211

211212
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).

0 commit comments

Comments
 (0)