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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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]