From 4cfa363e53169413f73efc0b22f8298569ba0af1 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 01/27] 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 c3786707fa..3a12372927 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 a05a487c94..26ac408795 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 043077e56e..203c9696a7 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] ## Labeled 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 2f397435bb1b86b023b874efec03dd923eb80d11 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Tue, 28 Oct 2025 22:07:00 +0000 Subject: [PATCH 02/27] 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 26ac408795..d23f8965e2 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 5eef4bcc01f19c3ad4d0298df0337d0655538361 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Sat, 1 Nov 2025 21:55:34 +0000 Subject: [PATCH 03/27] 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 d23f8965e2..9dc4f3b626 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 203c9696a7..c9e9539e7e 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] ## Labeled 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). From 8b6a6dafb7bf9229c7846145f5849308d7ef48f3 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Tue, 11 Nov 2025 20:49:47 +0000 Subject: [PATCH 04/27] Use functions returning ! in tests --- src/expressions/block-expr.md | 28 +++++++++++++++++----------- src/expressions/if-expr.md | 30 +++++++++++++++++------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 9dc4f3b626..6d43320c84 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -66,43 +66,49 @@ assert_eq!(5, five); 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), unless that expression is a place expression that is not read from. -```rust -# #![ feature(never_type) ] +```rust,no_run # fn make() -> T { loop {} } -let no_control_flow: ! = { +fn no_control_flow() -> ! { // There are no conditional statements, so this entire block is diverging. loop {} -}; +} -let control_flow_diverging: ! = { +fn control_flow_diverging() -> ! { // All paths are diverging, so this entire block is diverging. if true { loop {} } else { loop {} } -}; +} -let control_flow_not_diverging: () = { +fn 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: () = { +fn diverging_place_read() -> () { + let foo = Foo { x: make() }; + let _: ! = { + // A read of a place expression produces a diverging block + let _x = foo.x; + }; +} +fn diverging_place_not_read() -> () { + let foo = Foo { x: make() }; let _: () = { // Asssignment to `_` means the place is not read let _ = foo.x; }; -}; +} ``` r[expr.block.value] diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index 5a9658e637..d6ab08823b 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -76,20 +76,24 @@ 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 { - () -}; +```rust,no_run +fn diverging_condition() -> ! { + // Diverges because the condition expression diverges + if { loop {}; true } { + () + } else { + () + } +} -let x: ! = if true { - loop {} -} else { - loop {} -}; +fn diverging_arms() -> ! { + // Diverges because all arms diverge + if true { + loop {} + } else { + loop {} + } +} ``` r[expr.if.let] From d56f184e5333d0947cc96b858388f036559fcf2d Mon Sep 17 00:00:00 2001 From: jackh726 Date: Tue, 11 Nov 2025 21:23:20 +0000 Subject: [PATCH 05/27] Reviews from Jane --- src/divergence.md | 2 +- src/expressions/block-expr.md | 4 ++-- src/expressions/match-expr.md | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index 5936a4fe1b..dd46556d00 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -4,7 +4,7 @@ r[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!())`). +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(loop {})` produces a type of `Option`). r[divergence.fallback] ## Fallback diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 6d43320c84..00e8205a5c 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] -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. +Except in the case of divergence [(see below)](block-expr.md#r-expr.block.type.diverging), 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,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] -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. +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](../expressions.md#r-expr.place-value.place-memory-location) that is not read from. ```rust,no_run # fn make() -> T { loop {} } diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 7657e7d4b2..508ae8a33d 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -102,6 +102,19 @@ The type of the overall `match` expression is the [least upper bound](../type-co r[expr.match.empty] If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). +> [!EXAMPLE] +```rust +# fn make() -> T { loop {} } +enum Empty {} + +fn diverging_match_no_arms() -> ! { + let e: Empty = make(); + let + match e {} +} +``` + + r[expr.match.conditional] If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. From 90ef10f459f72b5c3d50a0409fba819c6c84527d Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 12 Nov 2025 15:27:24 +0000 Subject: [PATCH 06/27] Fix test - updating items.fn.implicit-return to be more specific --- src/expressions/block-expr.md | 2 +- src/expressions/if-expr.md | 6 ++++-- src/items/functions.md | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 00e8205a5c..a74e724792 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] -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](../expressions.md#r-expr.place-value.place-memory-location) that is not read from. +A block is itself considered to be [diverging](../divergence.md) if all reachable control flow paths contain a [diverging expression](../divergence.md), unless that expression is a [place expression](../expressions.md#r-expr.place-value.place-memory-location) that is not read from. ```rust,no_run # fn make() -> T { loop {} } diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index d6ab08823b..b3e97455ea 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -79,11 +79,13 @@ An `if` expression diverges if either the condition expression diverges or if al ```rust,no_run fn diverging_condition() -> ! { // Diverges because the condition expression diverges - if { loop {}; true } { + if loop {} { () } else { () - } + }; + // The semicolon above is important: + // The type of the `if` statement is `()`, despite being diverging. } fn diverging_arms() -> ! { diff --git a/src/items/functions.md b/src/items/functions.md index 468c50574f..70e95190b5 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -56,7 +56,7 @@ r[items.fn.signature] Functions may declare a set of *input* [*variables*][variables] as parameters, through which the caller passes arguments into the function, and the *output* [*type*][type] of the value the function will return to its caller on completion. r[items.fn.implicit-return] -If the output type is not explicitly stated, it is the [unit type]. +If the output type is not explicitly stated, it is the [unit type]. However, if the block expression is not [diverging](../divergence.md), then the output type is instead [`!`](../types/never.md). r[items.fn.fn-item-type] When referred to, a _function_ yields a first-class *value* of the corresponding zero-sized [*function item type*], which when called evaluates to a direct call to the function. From d582defcab591f64983fd4d26262badbe8500612 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 12 Nov 2025 17:56:34 +0000 Subject: [PATCH 07/27] Fix CI --- src/expressions/block-expr.md | 15 ++++++--------- src/expressions/match-expr.md | 19 +++++++++---------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index a74e724792..8d97f7cf6d 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -67,6 +67,7 @@ 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), unless that expression is a [place expression](../expressions.md#r-expr.place-value.place-memory-location) that is not read from. ```rust,no_run +# #![ feature(never_type) ] # fn make() -> T { loop {} } fn no_control_flow() -> ! { // There are no conditional statements, so this entire block is diverging. @@ -95,19 +96,15 @@ struct Foo { x: !, } -fn diverging_place_read() -> () { +fn diverging_place_read() -> ! { let foo = Foo { x: make() }; - let _: ! = { - // A read of a place expression produces a diverging block - let _x = foo.x; - }; + // A read of a place expression produces a diverging block + let _x = foo.x; } fn diverging_place_not_read() -> () { let foo = Foo { x: make() }; - let _: () = { - // Asssignment to `_` means the place is not read - let _ = foo.x; - }; + // Asssignment to `_` means the place is not read + let _ = foo.x; } ``` diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 508ae8a33d..35d8624b85 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -103,16 +103,15 @@ r[expr.match.empty] If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). > [!EXAMPLE] -```rust -# fn make() -> T { loop {} } -enum Empty {} - -fn diverging_match_no_arms() -> ! { - let e: Empty = make(); - let - match e {} -} -``` +> ```rust +> # fn make() -> T { loop {} } +> enum Empty {} +> +> fn diverging_match_no_arms() -> ! { +> let e: Empty = make(); +> match e {} +> } +> ``` r[expr.match.conditional] From eb381b73b380a79c70d7f2f15c4ecd5d19f17276 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 3 Dec 2025 17:32:48 +0000 Subject: [PATCH 08/27] Add section about uninhabited types in divergence.md --- src/divergence.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/divergence.md b/src/divergence.md index dd46556d00..020204355d 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -6,6 +6,25 @@ 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(loop {})` produces a type of `Option`). +> [!NOTE] +> Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. +> +> ```rust,compile_fail,E0308 +> # #![ feature(never_type) ] +> # fn make() -> T { loop {} } +> enum Empty {} +> fn diverging() -> ! { +> // This has a type of `!`. +> // So, the entire function is considered diverging +> make::(); +> } +> fn not_diverging() -> ! { +> // This type is uninhabited. +> // However, the entire function is not considered diverging +> make::(); +> } +> ``` + r[divergence.fallback] ## Fallback If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`. From e097e7ec2bc9c33fa792cf5644720259ee5dd0c6 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Wed, 3 Dec 2025 17:55:10 +0000 Subject: [PATCH 09/27] Address Niko's comments. --- src/divergence.md | 2 +- src/expressions/block-expr.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index 020204355d..da56a6ada8 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -2,7 +2,7 @@ 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. +If an expression diverges, then nothing after that expression will execute. 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 --- where divergence indicates that the block itself will never finish executing. 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(loop {})` produces a type of `Option`). diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 8d97f7cf6d..9da423ed05 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -70,12 +70,12 @@ A block is itself considered to be [diverging](../divergence.md) if all reachabl # #![ feature(never_type) ] # fn make() -> T { loop {} } fn no_control_flow() -> ! { - // There are no conditional statements, so this entire block is diverging. + // There are no conditional statements, so this entire function body is diverging. loop {} } fn control_flow_diverging() -> ! { - // All paths are diverging, so this entire block is diverging. + // All paths are diverging, so this entire function body is diverging. if true { loop {} } else { From ebc51fd9c60a0ab34857592dae6be0dec7881b46 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 20 Dec 2025 23:52:11 +0000 Subject: [PATCH 10/27] Make `diverging_place_not_read` subject to failure This test is meant to show that matching a diverging place against the wildcard pattern does not cause a read and therefore does not cause the block to diverge. This was shown by ascribing the return type of the function to unit. But due to never-to-any coercion, that will always type check, even if the read does take place. Let's instead ascribe the function return type to never and assert that the test must fail. This way, the test would fail if the read did occur. --- src/expressions/block-expr.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 9da423ed05..09513da049 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -101,11 +101,19 @@ fn diverging_place_read() -> ! { // A read of a place expression produces a diverging block let _x = foo.x; } -fn diverging_place_not_read() -> () { +``` + +```rust,compile_fail,E0308 +# #![ feature(never_type) ] +# fn make() -> T { loop {} } +# struct Foo { +# x: !, +# } +fn diverging_place_not_read() -> ! { let foo = Foo { x: make() }; // Asssignment to `_` means the place is not read let _ = foo.x; -} +} // ERROR: Mismatched types. ``` r[expr.block.value] From 895c0ddf3e5ab799a514c9eae00793f2e25feeb3 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:54:57 -0800 Subject: [PATCH 11/27] Consistently document and link diverging expressions This tries to add consistency to if, match, break, continue, return, and loop in how the rule for divergence (and their type). --- src/expressions/if-expr.md | 3 ++- src/expressions/loop-expr.md | 10 +++++++--- src/expressions/match-expr.md | 6 ++++-- src/expressions/return-expr.md | 6 +++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index b3e97455ea..4dd5f19cd8 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -74,7 +74,7 @@ assert_eq!(y, "Bigger"); ``` r[expr.if.diverging] -An `if` expression diverges if either the condition expression diverges or if all arms diverge. +An `if` expression [diverges] if either the condition expression diverges or if all arms diverge. ```rust,no_run fn diverging_condition() -> ! { @@ -205,4 +205,5 @@ r[expr.if.edition2024] [`match` expressions]: match-expr.md [boolean type]: ../types/boolean.md +[diverges]: divergence [scrutinee]: ../glossary.md#scrutinee diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md index c9e9539e7e..3d5b9420da 100644 --- a/src/expressions/loop-expr.md +++ b/src/expressions/loop-expr.md @@ -42,7 +42,7 @@ A `loop` expression repeats execution of its body continuously: `loop { println!("I live."); }`. r[expr.loop.infinite.diverging] -A `loop` expression without an associated `break` expression is diverging and has type [`!`](../types/never.md). +A `loop` expression without an associated `break` expression is [diverging] and has type [`!`]. r[expr.loop.infinite.break] A `loop` expression containing associated [`break` expression(s)](#break-expressions) may terminate, and must have type compatible with the value of the `break` expression(s). @@ -292,7 +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.diverging] +A `break` expression is [diverging] and has a type of [`!`]. r[expr.loop.break.label] A `break` expression is normally associated with the innermost `loop`, `for` or `while` loop enclosing the `break` expression, @@ -357,7 +358,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.diverging] +A `continue` expression is [diverging] and has a type of [`!`]. r[expr.loop.continue.while] In the case of a `while` loop, the head is the conditional operands controlling the loop. @@ -396,9 +398,11 @@ r[expr.loop.break-value.loop] In the case a `loop` has an associated `break`, it is not considered diverging, and the `loop` must have a type compatible with each `break` expression. `break` without an expression is considered identical to `break` with expression `()`. +[`!`]: type.never [`if` condition chains]: if-expr.md#chains-of-conditions [`if` expressions]: if-expr.md [`match` expression]: match-expr.md [boolean type]: ../types/boolean.md +[diverging]: divergence [scrutinee]: ../glossary.md#scrutinee [temporary values]: ../expressions.md#temporaries diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 35d8624b85..0b7c63eee1 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -100,7 +100,7 @@ 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.empty] -If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md). +If there are no match arms, then the `match` expression is [diverging] and the type is [`!`]. > [!EXAMPLE] > ```rust @@ -118,7 +118,7 @@ r[expr.match.conditional] If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. > [!NOTE] -> Even if 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 [`!`]. > >```rust,compile_fail,E0004 > let a = match true { @@ -193,9 +193,11 @@ The only attributes that have meaning on match arms are [`cfg`] and the [lint ch r[expr.match.attributes.inner] [Inner attributes] are allowed directly after the opening brace of the match expression in the same expression contexts as [attributes on block expressions]. +[`!`]: type.never [`cfg`]: ../conditional-compilation.md [attributes on block expressions]: block-expr.md#attributes-on-block-expressions [binding mode]: ../patterns.md#binding-modes +[diverging]: divergence [Inner attributes]: ../attributes.md [lint check attributes]: ../attributes/diagnostics.md#lint-check-attributes [pattern]: ../patterns.md diff --git a/src/expressions/return-expr.md b/src/expressions/return-expr.md index d07f95f927..67569d5c5f 100644 --- a/src/expressions/return-expr.md +++ b/src/expressions/return-expr.md @@ -12,7 +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). +r[expr.return.diverging] +A `return` expression is [diverging] and has a type of [`!`]. An example of a `return` expression: @@ -24,3 +25,6 @@ fn max(a: i32, b: i32) -> i32 { return b; } ``` + +[`!`]: type.never +[diverging]: divergence From 6475450e79db4c1759dfc1da88309ba8b48afa21 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:55:24 -0800 Subject: [PATCH 12/27] Fix misspelling in an example Also consistently uses periods in comments. --- src/expressions/block-expr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 09513da049..c3925990e9 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -98,7 +98,7 @@ struct Foo { fn diverging_place_read() -> ! { let foo = Foo { x: make() }; - // A read of a place expression produces a diverging block + // A read of a place expression produces a diverging block. let _x = foo.x; } ``` @@ -111,7 +111,7 @@ fn diverging_place_read() -> ! { # } fn diverging_place_not_read() -> ! { let foo = Foo { x: make() }; - // Asssignment to `_` means the place is not read + // Assignment to `_` means the place is not read. let _ = foo.x; } // ERROR: Mismatched types. ``` From d4a758cd4469fa493019f0f013cb75a77eccc9a2 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:57:13 -0800 Subject: [PATCH 13/27] Add note about use of an unstable nightly feature This helps make it clear that this is using special syntax that is normally not allowed. Also, unhide `make` so it isn't so mysterious what it is. --- src/expressions/block-expr.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index c3925990e9..5873c22468 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -68,7 +68,6 @@ A block is itself considered to be [diverging](../divergence.md) if all reachabl ```rust,no_run # #![ feature(never_type) ] -# fn make() -> T { loop {} } fn no_control_flow() -> ! { // There are no conditional statements, so this entire function body is diverging. loop {} @@ -92,10 +91,16 @@ fn control_flow_not_diverging() -> () { } } +// Note: This makes use of the unstable never type which is only available on +// Rust's nightly channel. This is done for illustration purposes. It is +// possible to encounter this scenario in stable Rust, but requires a more +// convoluted example. struct Foo { x: !, } +fn make() -> T { loop {} } + fn diverging_place_read() -> ! { let foo = Foo { x: make() }; // A read of a place expression produces a diverging block. From 635678b83cad5d89e8168a81ff5b703bfb797ef4 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:57:56 -0800 Subject: [PATCH 14/27] Clarify subtlety about `if` example semicolon --- src/expressions/if-expr.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index 4dd5f19cd8..65b43b29c6 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -84,8 +84,11 @@ fn diverging_condition() -> ! { } else { () }; - // The semicolon above is important: - // The type of the `if` statement is `()`, despite being diverging. + // The semicolon above is important: The type of the `if` expression is + // `()`, despite being diverging. When the final body expression is + // elided, the type of the body is inferred to ! because the function body + // diverges. Without the semicolon, the `if` would be the tail expression + // with type `()`, which would fail to match the return type `!`. } fn diverging_arms() -> ! { From 6590a440029af96ec8d24a55e014cba17bfdb4e6 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:58:29 -0800 Subject: [PATCH 15/27] Fix stray `>` --- src/divergence.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/divergence.md b/src/divergence.md index da56a6ada8..ec1ea6bda4 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -43,7 +43,8 @@ If a type to be inferred is only unified with diverging expressions, then that t > Before the 2024 edition, the type was inferred to instead be `()`. > [!NOTE] -> Importantly, type unification may happen *structurally*, so the fallback `!` may be part of a larger type. The > following compiles: +> 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 `!` From 876b7273641eab3c066af40df2583dcd2ebabca2 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 07:59:39 -0800 Subject: [PATCH 16/27] Remove use of nightly feature in an example This particular example can be written with stable Rust, and I don't think it detracts too much from what it is trying to illustrate. --- src/divergence.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index ec1ea6bda4..1dcf471fe1 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -10,18 +10,21 @@ Any expression of type [`!`](./types/never.md) is a _diverging expression_, but > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. > > ```rust,compile_fail,E0308 -> # #![ feature(never_type) ] -> # fn make() -> T { loop {} } > enum Empty {} +> fn make_never() -> ! {loop{}} +> fn make_empty() -> Empty {loop{}} +> > fn diverging() -> ! { > // This has a type of `!`. > // So, the entire function is considered diverging -> make::(); +> make_never(); +> // OK: The type of the body is `!` which matches the return type. > } > fn not_diverging() -> ! { > // This type is uninhabited. > // However, the entire function is not considered diverging -> make::(); +> make_empty(); +> // ERROR: The type of the body is `()` but expected type `!`. > } > ``` From a5923a28da3c5fd15afe07049202dd01b60dd926 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 08:05:36 -0800 Subject: [PATCH 17/27] Revert change to items.fn.implicit-return The addition here isn't quite right. See discussion at https://github.com/rust-lang/reference/pull/2067/changes#r2630177707. As a later followup, we should document that the type of the function block must be coercible into the return type of the function. (Or if there is a more generalized way to state that.) --- src/items/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/functions.md b/src/items/functions.md index 70e95190b5..468c50574f 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -56,7 +56,7 @@ r[items.fn.signature] Functions may declare a set of *input* [*variables*][variables] as parameters, through which the caller passes arguments into the function, and the *output* [*type*][type] of the value the function will return to its caller on completion. r[items.fn.implicit-return] -If the output type is not explicitly stated, it is the [unit type]. However, if the block expression is not [diverging](../divergence.md), then the output type is instead [`!`](../types/never.md). +If the output type is not explicitly stated, it is the [unit type]. r[items.fn.fn-item-type] When referred to, a _function_ yields a first-class *value* of the corresponding zero-sized [*function item type*], which when called evaluates to a direct call to the function. From 7d9541677d24e17a11c8a47ef3b70588b826d19e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 13:14:26 -0800 Subject: [PATCH 18/27] Clean up the links for expr.block.type.diverging --- src/expressions/block-expr.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 5873c22468..a9cabf6b2f 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] -A block is itself considered to be [diverging](../divergence.md) if all reachable control flow paths contain a [diverging expression](../divergence.md), unless that expression is a [place expression](../expressions.md#r-expr.place-value.place-memory-location) that is not read from. +A block is considered to be [diverging][divergence] if all reachable control flow paths contain a diverging expression, unless that expression is a [place expression] that is not read from. ```rust,no_run # #![ feature(never_type) ] @@ -350,6 +350,7 @@ fn is_unix_platform() -> bool { [inner attributes]: ../attributes.md [method]: ../items/associated-items.md#methods [mutable reference]: ../types/pointer.md#mutables-references- +[place expression]: expr.place-value.place-memory-location [scopes]: ../names/scopes.md [shared references]: ../types/pointer.md#shared-references- [statement]: ../statements.md From b48b527f6b07d5f4340e4548f023c8a25107000e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 13:15:35 -0800 Subject: [PATCH 19/27] Clarify the wording for expr.block.type Also clean up the links. --- src/expressions/block-expr.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index a9cabf6b2f..1c1c590b9f 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] -Except in the case of divergence [(see below)](block-expr.md#r-expr.block.type.diverging), the type of a block is the type of the final operand, or `()` if the final operand is omitted. +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.type.diverging], in which case it is the [never type]. ```rust # fn fn_call() {} @@ -350,6 +350,7 @@ fn is_unix_platform() -> bool { [inner attributes]: ../attributes.md [method]: ../items/associated-items.md#methods [mutable reference]: ../types/pointer.md#mutables-references- +[never type]: type.never [place expression]: expr.place-value.place-memory-location [scopes]: ../names/scopes.md [shared references]: ../types/pointer.md#shared-references- @@ -358,6 +359,7 @@ fn is_unix_platform() -> bool { [struct]: struct-expr.md [the lint check attributes]: ../attributes/diagnostics.md#lint-check-attributes [tuple expressions]: tuple-expr.md +[unit type]: 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 From c8fbfa1011680e9e0d66aaa5c2fa38cbb12eafd6 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 13:21:10 -0800 Subject: [PATCH 20/27] Clean up divergence chapter links --- src/divergence.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index 1dcf471fe1..ed94da2d5a 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -2,9 +2,9 @@ r[divergence] # Divergence r[divergence.intro] -If an expression diverges, then nothing after that expression will execute. 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 --- where divergence indicates that the block itself will never finish executing. +If an expression diverges, then nothing after that expression will execute. Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. -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(loop {})` produces a type of `Option`). +Any expression of type [`!`] is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(loop {})` produces a type of `Option`). > [!NOTE] > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. @@ -30,7 +30,8 @@ Any expression of type [`!`](./types/never.md) is a _diverging expression_, but r[divergence.fallback] ## Fallback -If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`. + +If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be [`!`]. > [!EXAMPLE] > ```rust,compile_fail,E0277 @@ -58,3 +59,5 @@ If a type to be inferred is only unified with diverging expressions, then that t > ``` + +[`!`]: type.never From ae7499e99c6f83af86bc9b252a50df4cf9a23961 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 13:31:41 -0800 Subject: [PATCH 21/27] Rename rule expr.block.diverging Rule names shouldn't be nested under other rules. --- src/expressions/block-expr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 1c1c590b9f..783c66eebe 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 its final operand; if that operand is omitted, the type is the [unit type], unless the block [diverges][expr.block.type.diverging], in which case it is the [never 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]. ```rust # fn fn_call() {} @@ -63,7 +63,7 @@ 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] +r[expr.block.diverging] A block is considered to be [diverging][divergence] if all reachable control flow paths contain a diverging expression, unless that expression is a [place expression] that is not read from. ```rust,no_run From d4de939c147383a101fb19bc6043138d576fab29 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 14:11:32 -0800 Subject: [PATCH 22/27] Rename expr.match.conditional I'm not sure what happened here. The rule was originally expr.match.type.diverging.conditional, then it was requested to change to expr.match.diverging. I think that suggestion fits well here, so let's go with it. --- src/expressions/match-expr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 0b7c63eee1..3ad873ed29 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -114,7 +114,7 @@ If there are no match arms, then the `match` expression is [diverging] and the t > ``` -r[expr.match.conditional] +r[expr.match.diverging] If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. > [!NOTE] From 4572f452785e1901a2eb3472a46b4f6a5e861edb Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 14:14:05 -0800 Subject: [PATCH 23/27] Consistently use periods in comments --- src/divergence.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index ed94da2d5a..d3c1895a30 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -16,13 +16,13 @@ Any expression of type [`!`] is a _diverging expression_, but there are also div > > fn diverging() -> ! { > // This has a type of `!`. -> // So, the entire function is considered diverging +> // So, the entire function is considered diverging. > make_never(); > // OK: The type of the body is `!` which matches the return type. > } > fn not_diverging() -> ! { > // This type is uninhabited. -> // However, the entire function is not considered diverging +> // However, the entire function is not considered diverging. > make_empty(); > // ERROR: The type of the body is `()` but expected type `!`. > } From 0e404259eef50b7b7f09f78f60aab7033c093be8 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 14:51:51 -0800 Subject: [PATCH 24/27] Rework the divergence intro This starts off the chapter with a direct definition of what a diverging expression is (see discussion in https://github.com/rust-lang/reference/pull/2067/changes#diff-11f5e85172f632195dd6a9a80daa852911fd3d66e788177a3bb39f6c5d7f21cfR10 about this definition). This also adds a very simple example showing divergence. Maybe not the greatest illustration, but I'd prefer to have at least a little something. --- src/divergence.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/divergence.md b/src/divergence.md index d3c1895a30..c587f7c436 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -2,7 +2,20 @@ r[divergence] # Divergence r[divergence.intro] -If an expression diverges, then nothing after that expression will execute. Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. +A *diverging expression* is an expression that never completes normal execution. + +```rust +fn diverges() -> ! { + panic!("This function never returns!"); +} + +fn example() { + let x: i32 = diverges(); // This line never completes. + println!("This is never printed: {x}"); +} +``` + +Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. Any expression of type [`!`] is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(loop {})` produces a type of `Option`). From da671a805e5511808d1f6cbae0deae9d851bd45c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 14:55:24 -0800 Subject: [PATCH 25/27] Add back the list of divergence rules This was asked to be changed or removed in https://github.com/rust-lang/reference/pull/2067#discussion_r2475872395 and was removed in 5eef4bcc01f19c3ad4d0298df0337d0655538361, but I think it is useful to include the links to the appropriate rules that are relevant here. This doesn't exactly duplicate any of the rules, it is just forwarding links, and this is something we do all over the Reference. Also add a mention about panics. I'm not sure if putting this in a note is really the right approach. However, we don't directly document `panic!` yet. We are currently in discussion about adding documentation for built-in macros. --- src/divergence.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/divergence.md b/src/divergence.md index c587f7c436..f43ad79ab0 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -15,6 +15,21 @@ fn example() { } ``` +See the following rules for specific expression divergence behavior: + +- [type.never.constraint] --- Function calls returning `!`. +- [expr.loop.infinite.diverging] --- Infinite `loop` expressions. +- [expr.loop.break.diverging] --- `break` expressions. +- [expr.loop.continue.diverging] --- `continue` expressions. +- [expr.return.diverging] --- `return` expressions. +- [expr.if.diverging] --- `if` expressions. +- [expr.match.diverging] --- `match` expressions. +- [expr.match.empty] --- Empty `match` expressions. +- [expr.block.diverging] --- Block expressions. + +> [!NOTE] +> The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging. + Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. Any expression of type [`!`] is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(loop {})` produces a type of `Option`). From 87c2fc68ce773997de0803968f13a37cb6c1e247 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 14:57:20 -0800 Subject: [PATCH 26/27] Add divergence.never Add a rule identifier to this particular statement. Also some slight rewording for clarity. --- src/divergence.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/divergence.md b/src/divergence.md index f43ad79ab0..d77bd0b6a7 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -32,7 +32,8 @@ See the following rules for specific expression divergence behavior: Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. -Any expression of type [`!`] is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(loop {})` produces a type of `Option`). +r[divergence.never] +Any expression of type [`!`] is a diverging expression. However, diverging expressions are not limited to type [`!`]; expressions of other types may also diverge (e.g., `Some(loop {})` has type `Option`). > [!NOTE] > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. From 50def6f55e7e9200aa8e19da02de4f7cfb7c684c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 17 Jan 2026 15:00:17 -0800 Subject: [PATCH 27/27] Move propagation statement to a note For the most part, this seemed to be restating the expr.block.diverging rule. (At least, I couldn't determine if it was expressing something distinct.) This is an interesting property, but one that I think is already covered. So, relegate it to a note linking to the relevant text. --- src/divergence.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index d77bd0b6a7..bce4cc3a69 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -30,8 +30,6 @@ See the following rules for specific expression divergence behavior: > [!NOTE] > The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging. -Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`], divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. - r[divergence.never] Any expression of type [`!`] is a diverging expression. However, diverging expressions are not limited to type [`!`]; expressions of other types may also diverge (e.g., `Some(loop {})` has type `Option`). @@ -57,6 +55,9 @@ Any expression of type [`!`] is a diverging expression. However, diverging expre > } > ``` +> [!NOTE] +> Divergence can propagate to the surrounding block. See [expr.block.diverging]. + r[divergence.fallback] ## Fallback