Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
00f0849
Write chapter on divergence
jackh726 Sep 9, 2025
ee00f3f
Slight edits from lcnr's review
jackh726 Oct 28, 2025
f1e8ba2
Address some review comments
jackh726 Nov 1, 2025
7cf2333
Use functions returning ! in tests
jackh726 Nov 11, 2025
c7dbc80
Reviews from Jane
jackh726 Nov 11, 2025
bd2507c
Fix test - updating items.fn.implicit-return to be more specific
jackh726 Nov 12, 2025
87e1ebe
Fix CI
jackh726 Nov 12, 2025
36d2d33
Add section about uninhabited types in divergence.md
jackh726 Dec 3, 2025
0bc8eb8
Address Niko's comments.
jackh726 Dec 3, 2025
76a6390
Make `diverging_place_not_read` subject to failure
traviscross Dec 20, 2025
b398a52
Consistently document and link diverging expressions
ehuss Jan 17, 2026
d6339e9
Fix misspelling in an example
ehuss Jan 17, 2026
92cfc9b
Add note about use of an unstable nightly feature
ehuss Jan 17, 2026
4eecc7e
Clarify subtlety about `if` example semicolon
ehuss Jan 17, 2026
3e64b91
Fix stray `>`
ehuss Jan 17, 2026
1e95e31
Remove use of nightly feature in an example
ehuss Jan 17, 2026
9e273b9
Revert change to items.fn.implicit-return
ehuss Jan 17, 2026
6606f21
Clean up the links for expr.block.type.diverging
ehuss Jan 17, 2026
9b51cb0
Clarify the wording for expr.block.type
ehuss Jan 17, 2026
b20b749
Clean up divergence chapter links
ehuss Jan 17, 2026
5e4cd4e
Rename rule expr.block.diverging
ehuss Jan 17, 2026
5c0c215
Rename expr.match.conditional
ehuss Jan 17, 2026
2a53c3a
Consistently use periods in comments
ehuss Jan 17, 2026
afc201a
Rework the divergence intro
ehuss Jan 17, 2026
e8c69e7
Add back the list of divergence rules
ehuss Jan 17, 2026
e4a4abb
Add divergence.never
ehuss Jan 17, 2026
071b165
Move propagation statement to a note
ehuss Jan 17, 2026
93c063e
Add rule expr.loop.break-value.implicit-value
ehuss Jan 29, 2026
6c7c380
Specify the type of a labeled block expression
ehuss Jan 29, 2026
1948110
More clearly specify the type and divergence of a loop with break
ehuss Jan 29, 2026
67ce5c1
Add some missing expressions for the LUB coercion list
ehuss Jan 29, 2026
92fc80a
Move link to link definition
ehuss Jan 29, 2026
d9d1c1b
Remove note about match divergence and `!`
ehuss Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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)

Expand Down
95 changes: 95 additions & 0 deletions src/divergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
r[divergence]
# Divergence

r[divergence.intro]
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}");
}
```

See the following rules for specific expression divergence behavior:

- [expr.block.diverging] --- Block expressions.
- [expr.if.diverging] --- `if` expressions.
- [expr.loop.block-labels.type] --- Labeled block expressions with `break`.
- [expr.loop.break-value.diverging] --- `loop` expressions with `break`.
- [expr.loop.break.diverging] --- `break` expressions.
- [expr.loop.continue.diverging] --- `continue` expressions.
- [expr.loop.infinite.diverging] --- Infinite `loop` expressions.
- [expr.match.diverging] --- `match` expressions.
- [expr.match.empty] --- Empty `match` expressions.
- [expr.return.diverging] --- `return` expressions.
- [type.never.constraint] --- Function calls returning `!`.

> [!NOTE]
> The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging.

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.
>
> ```rust,compile_fail,E0308
> 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_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_empty();
> // ERROR: The type of the body is `()` but expected type `!`.
> }
> ```

> [!NOTE]
> Divergence can propagate to the surrounding block. See [expr.block.diverging].

r[divergence.fallback]
## Fallback

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
> 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 `()`.

> [!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),
> };
> ```

<!-- TODO: This last point should likely should be moved to a more general "type inference" section discussing generalization + unification. -->

[`!`]: type.never
63 changes: 62 additions & 1 deletion src/expressions/block-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,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.
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].
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change isn't correct because of #2067 (comment)

In that example, the final operand is loop {}, which has a type of !. But, the type of the entire block is usize. I think I maybe miscommunicated when I said "I like your wording" - because it seems nice, but has this flaw.

My comment at the end is essentially the rule that needs to be here:
The type of the block ends up being the LUB of all the break (or return for fns) types, and the final expression (which depends on whether the block is diverging, whether it is () or !).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was what I was getting at when I said it could be a followup, since I don't fully understand all the bits here. The problem is that blocks with break expressions are treated separately from normal blocks in the reference, and we need to fit this all together. I tried with the following:

diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md
index 3d5b9420..bfe08687 100644
--- a/src/expressions/loop-expr.md
+++ b/src/expressions/loop-expr.md
@@ -311,6 +311,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-value.implicit-value]
+In the context of a [`loop` with break expressions][expr.loop.break-value] or a [labeled block expression], a `break` without an expression is considered identical to `break` with expression `()`.
+
 r[expr.loop.block-labels]
 ## Labeled block expressions

@@ -347,6 +350,24 @@ let result = 'block: {
 };
 ```

+r[expr.loop.block-labels.type]
+The type of a labeled block expression is the [least upper bound] of all of the break operands, and the final operand. If the final operand is omitted, the type of the final operand defaults to the [unit type], unless the block [diverges][expr.block.diverging], in which case it is the [never type].
+
+> [!EXAMPLE]
+> ```rust
+> fn example(condition: bool) {
+>     let s = String::from("owned");
+>
+>     let _: &str = 'block: {
+>         if condition {
+>             break 'block &s;  // &String coerced to &str via Deref
+>         }
+>         break 'block "literal";  // &'static str coerced to &str
+>     };
+> }
+> ```
+
+
 r[expr.loop.continue]
 ## `continue` expressions

@@ -394,15 +415,33 @@ let result = loop {
 assert_eq!(result, 13);
 ```

-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 `()`.
+r[expr.loop.break-value.type]
+A `loop` with an associated `break` does not [diverge], and its type is the [least upper bound] of all of the break operands.
+
+> [!EXAMPLE]
+> ```rust
+> fn example(condition: bool) {
+>     let s = String::from("owned");
+>
+>     let _: &str = loop {
+>         if condition {
+>             break &s; // &String coerced to &str via Deref
+>         }
+>         break "literal"; // &'static str coerced to &str
+>     };
+> }
+> ```

 [`!`]: 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
+[diverge]: divergence
 [diverging]: divergence
+[labeled block expression]: expr.loop.block-labels
+[least upper bound]: coerce.least-upper-bound
+[never type]: type.never
 [scrutinee]: ../glossary.md#scrutinee
 [temporary values]: ../expressions.md#temporaries
+[unit type]: type.tuple.unit
diff --git a/src/type-coercions.md b/src/type-coercions.md
index 91b773ee..6bef99c3 100644
--- a/src/type-coercions.md
+++ b/src/type-coercions.md
@@ -239,6 +239,8 @@ LUB coercion is used and only used in the following situations:
 + To find the common type for a series of if branches.
 + To find the common type for a series of match arms.
 + To find the common type for array elements.
++ To find the common type for a [labeled block expression] among the break operands and the final block operand.
++ To find the common type for an [`loop` expression with break expressions] among the break operands.
 + To find the type for the return type of a closure with multiple return statements.
 + To check the type for the return type of a function with multiple return statements.

@@ -325,5 +327,7 @@ precisely.
 [type cast operator]: expressions/operator-expr.md#type-cast-expressions
 [`Unsize`]: std::marker::Unsize
 [`CoerceUnsized`]: std::ops::CoerceUnsized
+[labeled block expression]: expr.loop.block-labels
+[`loop` expression with break expressions]: expr.loop.break-value
 [method-call expressions]: expressions/method-call-expr.md
 [supertraits]: items/traits.md#supertraits

Does that look correct?

One rule I was particularly uncertain about was expr.loop.break-value.type. The statement "loop…does not diverge"" seems too strong. Does a loop with breaks that panics in all control flows, or break with diverging expressions, itself diverge? Does it require at least one control path to have a break without a diverging expression? If so, then it seems like "does not diverge" is too strongly worded. But I'm not sure how to word that. I'm wondering if it needs to be worded closer to how expr.block.diverging is done.

I'm also just guessing that the additions to the LUB list make sense.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That diff looks good to me for the most part.

expr.loop.break-value.type is not correct though, for the reason you hinted at:

fn example() {
    let _: ! = loop {
        break loop {};
    };
}

This is the particular logic: https://github.com/rust-lang/rust/blob/ba2a7d33741a7ade4dc78e5e335d60d358cd1749/compiler/rustc_hir_typeck/src/expr.rs#L826

(may_break is only true if the break operand is not diverging)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording here can be something like

A loop does not diverge if it contains any break expressions with an operand that does not diverge, and its type is the [least upper bound] of all of the break operands.


```rust
# fn fn_call() {}
Expand All @@ -62,6 +62,64 @@ 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.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
# #![ feature(never_type) ]
fn no_control_flow() -> ! {
// There are no conditional statements, so this entire function body is diverging.
loop {}
}

fn control_flow_diverging() -> ! {
// All paths are diverging, so this entire function body is diverging.
if true {
loop {}
} else {
loop {}
}
}

fn control_flow_not_diverging() -> () {
// Some paths are not diverging, so this entire block is not diverging.
if true {
()
} else {
loop {}
}
}

// 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: !,
}
Comment on lines +97 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be rewritten to use an empty enum to avoid the need for nightly features? For example:

Suggested change
struct Foo {
x: !,
}
enum Empty {}
struct Foo {
x: Empty,
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


fn make<T>() -> T { loop {} }

fn diverging_place_read() -> ! {
let foo = Foo { x: make() };
// A read of a place expression produces a diverging block.
let _x = foo.x;
}
```

```rust,compile_fail,E0308
# #![ feature(never_type) ]
# fn make<T>() -> T { loop {} }
# struct Foo {
# x: !,
# }
fn diverging_place_not_read() -> ! {
let foo = Foo { x: make() };
// Assignment to `_` means the place is not read.
let _ = foo.x;
} // ERROR: Mismatched types.
```

r[expr.block.value]
Blocks are always [value expressions] and evaluate the last operand in value expression context.

Expand Down Expand Up @@ -279,13 +337,16 @@ 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-
[statement]: ../statements.md
[statements]: ../statements.md
[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
29 changes: 29 additions & 0 deletions src/expressions/if-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ 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,no_run
fn diverging_condition() -> ! {
// Diverges because the condition expression diverges
if loop {} {
()
} else {
()
};
// 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() -> ! {
// Diverges because all arms diverge
if true {
loop {}
} else {
loop {}
}
}
```

r[expr.if.let]
## `if let` patterns

Expand Down Expand Up @@ -174,4 +202,5 @@ r[expr.if.edition2024]

[`match` expressions]: match-expr.md
[boolean type]: ../types/boolean.md
[diverges]: divergence
[scrutinee]: ../glossary.md#scrutinee
86 changes: 82 additions & 4 deletions src/expressions/loop-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ r[expr.loop.infinite.intro]
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).
Expand Down Expand Up @@ -282,6 +282,9 @@ for x in 1..100 {
assert_eq!(last, 12);
```

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, but a [label](#loop-labels) can be used to specify which enclosing loop is affected. Example:

Expand All @@ -296,6 +299,9 @@ A `break` expression is normally associated with the innermost `loop`, `for` or
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-value.implicit-value]
In a [`loop` with break expressions][expr.loop.break-value] or a [labeled block expression], a `break` without an expression is equivalent to `break ()`.

r[expr.loop.block-labels]
## Labeled block expressions

Expand Down Expand Up @@ -332,6 +338,23 @@ let result = 'block: {
};
```

r[expr.loop.block-labels.type]
The type of a labeled block expression is the [least upper bound] of all of the break operands and the final operand. If the final operand is omitted, the type of the final operand defaults to the [unit type], unless the block [diverges][expr.block.diverging], in which case it is the [never type].

> [!EXAMPLE]
> ```rust
> fn example(condition: bool) {
> let s = String::from("owned");
>
> let _: &str = 'block: {
> if condition {
> break 'block &s; // &String coerced to &str via Deref
> }
> break 'block "literal"; // &'static str coerced to &str
> };
> }
> ```

r[expr.loop.continue]
## `continue` expressions

Expand All @@ -343,6 +366,9 @@ 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*.

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.

Expand Down Expand Up @@ -375,12 +401,64 @@ let result = loop {
assert_eq!(result, 13);
```

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 `()`.

r[expr.loop.break-value.type]
The type of a `loop` with associated `break` expressions is the [least upper bound] of all of the break operands.

> [!EXAMPLE]
> ```rust
> fn example(condition: bool) {
> let s = String::from("owned");
>
> let _: &str = loop {
> if condition {
> break &s; // &String coerced to &str via Deref
> }
> break "literal"; // &'static str coerced to &str
> };
> }
> ```

r[expr.loop.break-value.diverging]
A `loop` with associated `break` expressions does not [diverge] if any of the break operands do not diverge. If all of the `break` operands diverge, then the `loop` expression also diverges.

> [!EXAMPLE]
> ```rust
> fn diverging_loop_with_break(condition: bool) -> ! {
> // This loop is diverging because all `break` operands are diverging.
> loop {
> if condition {
> break loop {};
> } else {
> break panic!();
> }
> }
> }
> ```
>
> ```rust,compile_fail,E0308
> fn loop_with_non_diverging_break(condition: bool) -> ! {
> // The type of this loop is i32 even though one of the breaks is
> // diverging.
> loop {
> if condition {
> break loop {};
> } else {
> break 123i32;
> }
> } // ERROR: expected `!`, found `i32`
> }
> ```

[`!`]: 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
[diverge]: divergence
[diverging]: divergence
[labeled block expression]: expr.loop.block-labels
[least upper bound]: coerce.least-upper-bound
[never type]: type.never
[scrutinee]: ../glossary.md#scrutinee
[temporary values]: ../expressions.md#temporaries
[unit type]: type.tuple.unit
Loading