From 84a8e3010711a0828af1dc5e4a4a7cd1cda5bb5f Mon Sep 17 00:00:00 2001 From: Jack Huey <31162821+jackh726@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:44:54 -0400 Subject: [PATCH 1/3] Write chapter on divergence --- src/SUMMARY.md | 1 + src/divergence.md | 52 ++++++++++++++++++++++++++++++++++ src/expressions/block-expr.md | 5 +++- src/expressions/loop-expr.md | 6 ++++ src/expressions/match-expr.md | 22 ++++++++++++++ src/expressions/return-expr.md | 3 ++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/divergence.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 977ad581e2..3d426f34c9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -97,6 +97,7 @@ - [Subtyping and variance](subtyping.md) - [Trait and lifetime bounds](trait-bounds.md) - [Type coercions](type-coercions.md) + - [Divergence](divergence.md) - [Destructors](destructors.md) - [Lifetime elision](lifetime-elision.md) diff --git a/src/divergence.md b/src/divergence.md new file mode 100644 index 0000000000..3fb5128cd4 --- /dev/null +++ b/src/divergence.md @@ -0,0 +1,52 @@ +r[divergence] +# Divergence + +r[divergence.intro] +Divergence is the state where a particular section of code could never be encountered at runtime. Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`](./types/never.md), divergence can also propogate to the surrounding block. + +Any expression of type [`!`](./types/never.md) is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(panic!())`). + +r[divergence.diverging-expressions] +## Producing diverging expressions + +r[divergence.diverging-expressions.unconditional] +The following language constructs unconditonally produce a _diverging expression_ of the type [`!`](./types/never.md): + +* [A call to a function returning `!`.](./types/never.md#r-type.never.constraint) +* [A `loop` expression with no corresponding break.](./expressions/loop-expr.md#r-expr.loop.infinite.diverging) +* [A `break` expression](./expressions/loop-expr.md#r-expr.loop.break.type) +* [A `continue` expression](./expressions/loop-expr.md#r-expr.loop.continue.type) +* [A `return` expression](./expressions/return-expr.md#r-expr.return.type) +* [A `match` expression with no arms](./expressions/match-expr.md#r-expr.match.type.diverging.empty) +* [A `block` expression that it itself is diverging.](../expressions/block-expr.md#r-expr.block.type.diverging) + +r[divergence.diverging-expressions.conditional] +In a control flow expression, if all arms diverge, then the entire expression also diverges. + +r[divergence.fallback] +## Fallback +If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`. + +The following fails to compile because `!` does not implement `Debug`: +```rust,compile_fail,E0277 +fn foo() -> i32 { 22 } +match foo() { + 4 => Default::default(), + _ => return, +}; +``` + +> [!EDITION-2024] +> Before the 2024 edition, the type was inferred to instead be `()`. + +Importantly, type unification may happen *structurally*, so the fallback `!` may be part of a larger type. The following compiles: +```rust +fn foo() -> i32 { 22 } +// This has the type `Option`, not `!` +match foo() { + 4 => Default::default(), + _ => Some(return), +}; +``` + + diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index da0f93b368..96e79743bf 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -44,7 +44,7 @@ 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 the final operand, or `()` if the final operand is omitted. +Typically, the type of a block is the type of the final operand, or `()` if the final operand is omitted. ```rust # fn fn_call() {} @@ -63,6 +63,9 @@ 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. +r[expr.block.type.diverging] +However, if there are any values unconditionally created within a block that are [diverging](../divergence.md), then the block itself is considered diverging. + r[expr.block.value] Blocks are always [value expressions] and evaluate the last operand in value expression context. diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md index 6dc669ad39..a5c817fa52 100644 --- a/src/expressions/loop-expr.md +++ b/src/expressions/loop-expr.md @@ -308,6 +308,9 @@ Example: r[expr.loop.break.value] A `break` expression is only permitted in the body of a loop, and has one of the forms `break`, `break 'label` or ([see below](#break-and-loop-values)) `break EXPR` or `break 'label EXPR`. +r[expr.loop.break.type] +A `break` expression itself has a type of [`!`](../types/never.md). + r[expr.loop.block-labels] ## Labelled block expressions @@ -367,6 +370,9 @@ Like `break`, `continue` is normally associated with the innermost enclosing loo r[expr.loop.continue.in-loop-only] A `continue` expression is only permitted in the body of a loop. +r[expr.loop.continue.type] +A `continue` expression itself has a type of [`!`](../types/never.md). + r[expr.loop.break-value] ## `break` and loop values diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 5bfbbc76db..23880693a8 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -96,6 +96,28 @@ Every binding in each `|` separated pattern must appear in all of the patterns i r[expr.match.binding-restriction] Every binding of the same name must have the same type, and have the same binding mode. +r[expr.match.type] +The type of the overall `match` expression is the least upper bound of the individual match arms. + +r[expr.match.type.diverging.empty] +If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). + +r[expr.match.type.diverging.conditional] +If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. + +> [!NOTE] +> If even the entire `match` expression diverges, its type may not be [`!`](../types/never.md). +> +>```rust,compile_fail,E0004 +> let a = match true { +> true => Some(panic!()), +> false => None, +> }; +> // Fails to compile because `a` has the type `Option` +> // (or, `Option<()>` in edition 2021 and below) +> match a {} +>``` + r[expr.match.guard] ## Match guards diff --git a/src/expressions/return-expr.md b/src/expressions/return-expr.md index ee8f59d055..2c5ebd59e4 100644 --- a/src/expressions/return-expr.md +++ b/src/expressions/return-expr.md @@ -22,3 +22,6 @@ fn max(a: i32, b: i32) -> i32 { return b; } ``` + +r[expr.return.type] +A `return` expression itself has a type of [`!`](../types/never.md). From 2403b187e088f5f69b6c22e213e561223629b578 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Tue, 28 Oct 2025 22:07:00 +0000 Subject: [PATCH 2/3] Slight edits from lcnr's review --- src/divergence.md | 5 ++++- src/expressions/block-expr.md | 2 +- src/expressions/match-expr.md | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index 3fb5128cd4..a5f6dc9e34 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -18,11 +18,14 @@ The following language constructs unconditonally produce a _diverging expression * [A `continue` expression](./expressions/loop-expr.md#r-expr.loop.continue.type) * [A `return` expression](./expressions/return-expr.md#r-expr.return.type) * [A `match` expression with no arms](./expressions/match-expr.md#r-expr.match.type.diverging.empty) -* [A `block` expression that it itself is diverging.](../expressions/block-expr.md#r-expr.block.type.diverging) +* [A `block` expression that it itself is diverging.](./expressions/block-expr.md#r-expr.block.type.diverging) r[divergence.diverging-expressions.conditional] In a control flow expression, if all arms diverge, then the entire expression also diverges. +r[divergence.diverging-expressions.place-expressions] +A place expression of the type [`!`](./types/never.md) is considering _diverging_ only if it is read from. + r[divergence.fallback] ## Fallback If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`. diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 96e79743bf..48854e6749 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -64,7 +64,7 @@ assert_eq!(5, five); > 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. r[expr.block.type.diverging] -However, if there are any values unconditionally created within a block that are [diverging](../divergence.md), then the block itself is considered diverging. +A block is itself considered to be [diverging](../divergence.md) if all reachable control flow paths contain a [diverging expression](../divergence.md#r-divergence.diverging-expressions). r[expr.block.value] Blocks are always [value expressions] and evaluate the last operand in value expression context. diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 23880693a8..d2ec777d6e 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -97,7 +97,7 @@ r[expr.match.binding-restriction] Every binding of the same name must have the same type, and have the same binding mode. r[expr.match.type] -The type of the overall `match` expression is the least upper bound of the individual match arms. +The type of the overall `match` expression is the [least upper bound](../type-coercions.md#r-coerce.least-upper-bound) of the individual match arms. r[expr.match.type.diverging.empty] If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). From a9d8264d54a94e3209af0ff7330dbd09b7e2c40c Mon Sep 17 00:00:00 2001 From: jackh726 Date: Sat, 1 Nov 2025 21:55:34 +0000 Subject: [PATCH 3/3] Address some review comments --- src/divergence.md | 56 ++++++++++++---------------------- src/expressions/block-expr.md | 43 ++++++++++++++++++++++++-- src/expressions/if-expr.md | 19 ++++++++++++ src/expressions/loop-expr.md | 10 +++--- src/expressions/match-expr.md | 9 +++--- src/expressions/return-expr.md | 5 ++- 6 files changed, 89 insertions(+), 53 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index a5f6dc9e34..5936a4fe1b 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -6,50 +6,32 @@ Divergence is the state where a particular section of code could never be encoun Any expression of type [`!`](./types/never.md) is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(panic!())`). -r[divergence.diverging-expressions] -## Producing diverging expressions - -r[divergence.diverging-expressions.unconditional] -The following language constructs unconditonally produce a _diverging expression_ of the type [`!`](./types/never.md): - -* [A call to a function returning `!`.](./types/never.md#r-type.never.constraint) -* [A `loop` expression with no corresponding break.](./expressions/loop-expr.md#r-expr.loop.infinite.diverging) -* [A `break` expression](./expressions/loop-expr.md#r-expr.loop.break.type) -* [A `continue` expression](./expressions/loop-expr.md#r-expr.loop.continue.type) -* [A `return` expression](./expressions/return-expr.md#r-expr.return.type) -* [A `match` expression with no arms](./expressions/match-expr.md#r-expr.match.type.diverging.empty) -* [A `block` expression that it itself is diverging.](./expressions/block-expr.md#r-expr.block.type.diverging) - -r[divergence.diverging-expressions.conditional] -In a control flow expression, if all arms diverge, then the entire expression also diverges. - -r[divergence.diverging-expressions.place-expressions] -A place expression of the type [`!`](./types/never.md) is considering _diverging_ only if it is read from. - r[divergence.fallback] ## Fallback If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`. -The following fails to compile because `!` does not implement `Debug`: -```rust,compile_fail,E0277 -fn foo() -> i32 { 22 } -match foo() { - 4 => Default::default(), - _ => return, -}; -``` +> [!EXAMPLE] +> ```rust,compile_fail,E0277 +> fn foo() -> i32 { 22 } +> match foo() { +> // ERROR: The trait bound `!: Default` is not satisfied. +> 4 => Default::default(), +> _ => return, +> }; +> ``` > [!EDITION-2024] > Before the 2024 edition, the type was inferred to instead be `()`. -Importantly, type unification may happen *structurally*, so the fallback `!` may be part of a larger type. The following compiles: -```rust -fn foo() -> i32 { 22 } -// This has the type `Option`, not `!` -match foo() { - 4 => Default::default(), - _ => Some(return), -}; -``` +> [!NOTE] +> Importantly, type unification may happen *structurally*, so the fallback `!` may be part of a larger type. The > following compiles: +> ```rust +> fn foo() -> i32 { 22 } +> // This has the type `Option`, not `!` +> match foo() { +> 4 => Default::default(), +> _ => Some(return), +> }; +> ``` diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 48854e6749..7bd208bab7 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -44,7 +44,7 @@ r[expr.block.result] Then the final operand is executed, if given. r[expr.block.type] -Typically, the type of a block is the type of the final operand, or `()` if the final operand is omitted. +Except in the case of divergence (see below), the type of a block is the type of the final operand, or `()` if the final operand is omitted. ```rust # fn fn_call() {} @@ -64,7 +64,46 @@ assert_eq!(5, five); > 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. r[expr.block.type.diverging] -A block is itself considered to be [diverging](../divergence.md) if all reachable control flow paths contain a [diverging expression](../divergence.md#r-divergence.diverging-expressions). +A block is itself considered to be [diverging](../divergence.md) if all reachable control flow paths contain a [diverging expression](../divergence.md#r-divergence.diverging-expressions), unless that expression is a place expression that is not read from. + +```rust +# #![ feature(never_type) ] +# fn make() -> T { loop {} } +let no_control_flow: ! = { + // There are no conditional statements, so this entire block is diverging. + loop {} +}; + +let control_flow_diverging: ! = { + // All paths are diverging, so this entire block is diverging. + if true { + loop {} + } else { + loop {} + } +}; + +let control_flow_not_diverging: () = { + // Some paths are not diverging, so this entire block is not diverging. + if true { + () + } else { + loop {} + } +}; + +struct Foo { + x: !, +} + +let foo = Foo { x: make() }; +let diverging_place_not_read: () = { + let _: () = { + // Asssignment to `_` means the place is not read + let _ = foo.x; + }; +}; +``` r[expr.block.value] Blocks are always [value expressions] and evaluate the last operand in value expression context. diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index 46636112f7..5a9658e637 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -73,6 +73,25 @@ let y = if 12 * 15 > 150 { assert_eq!(y, "Bigger"); ``` +r[expr.if.diverging] +An `if` expression diverges if either the condition expression diverges or if all arms diverge. + +```rust +# #![ feature(never_type) ] +// Diverges because the condition expression diverges +let x: ! = if { loop {}; true } { + () +} else { + () +}; + +let x: ! = if true { + loop {} +} else { + loop {} +}; +``` + r[expr.if.let] ## `if let` patterns diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md index a5c817fa52..87aeb8d632 100644 --- a/src/expressions/loop-expr.md +++ b/src/expressions/loop-expr.md @@ -292,6 +292,8 @@ for x in 1..100 { assert_eq!(last, 12); ``` +Thus, the `break` expression itself is diverging and has a type of [`!`](../types/never.md). + r[expr.loop.break.label] A `break` expression is normally associated with the innermost `loop`, `for` or `while` loop enclosing the `break` expression, but a [label](#loop-labels) can be used to specify which enclosing loop is affected. @@ -308,9 +310,6 @@ Example: r[expr.loop.break.value] A `break` expression is only permitted in the body of a loop, and has one of the forms `break`, `break 'label` or ([see below](#break-and-loop-values)) `break EXPR` or `break 'label EXPR`. -r[expr.loop.break.type] -A `break` expression itself has a type of [`!`](../types/never.md). - r[expr.loop.block-labels] ## Labelled block expressions @@ -358,6 +357,8 @@ ContinueExpression -> `continue` LIFETIME_OR_LABEL? r[expr.loop.continue.intro] When `continue` is encountered, the current iteration of the associated loop body is immediately terminated, returning control to the loop *head*. +Thus, the `continue` expression itself has a type of [`!`](../types/never.md). + r[expr.loop.continue.while] In the case of a `while` loop, the head is the conditional operands controlling the loop. @@ -370,9 +371,6 @@ Like `break`, `continue` is normally associated with the innermost enclosing loo r[expr.loop.continue.in-loop-only] A `continue` expression is only permitted in the body of a loop. -r[expr.loop.continue.type] -A `continue` expression itself has a type of [`!`](../types/never.md). - r[expr.loop.break-value] ## `break` and loop values diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index d2ec777d6e..7657e7d4b2 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -99,22 +99,21 @@ Every binding of the same name must have the same type, and have the same bindin r[expr.match.type] The type of the overall `match` expression is the [least upper bound](../type-coercions.md#r-coerce.least-upper-bound) of the individual match arms. -r[expr.match.type.diverging.empty] +r[expr.match.empty] If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). -r[expr.match.type.diverging.conditional] +r[expr.match.conditional] If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. > [!NOTE] -> If even the entire `match` expression diverges, its type may not be [`!`](../types/never.md). +> Even if the entire `match` expression diverges, its type may not be [`!`](../types/never.md). > >```rust,compile_fail,E0004 > let a = match true { > true => Some(panic!()), > false => None, > }; -> // Fails to compile because `a` has the type `Option` -> // (or, `Option<()>` in edition 2021 and below) +> // Fails to compile because `a` has the type `Option`. > match a {} >``` diff --git a/src/expressions/return-expr.md b/src/expressions/return-expr.md index 2c5ebd59e4..d07f95f927 100644 --- a/src/expressions/return-expr.md +++ b/src/expressions/return-expr.md @@ -12,6 +12,8 @@ Return expressions are denoted with the keyword `return`. r[expr.return.behavior] Evaluating a `return` expression moves its argument into the designated output location for the current function call, destroys the current function activation frame, and transfers control to the caller frame. +Thus, a `return` expression itself has a type of [`!`](../types/never.md). + An example of a `return` expression: ```rust @@ -22,6 +24,3 @@ fn max(a: i32, b: i32) -> i32 { return b; } ``` - -r[expr.return.type] -A `return` expression itself has a type of [`!`](../types/never.md).