Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4cfa363
Write chapter on divergence
jackh726 Sep 9, 2025
2f39743
Slight edits from lcnr's review
jackh726 Oct 28, 2025
5eef4bc
Address some review comments
jackh726 Nov 1, 2025
8b6a6da
Use functions returning ! in tests
jackh726 Nov 11, 2025
d56f184
Reviews from Jane
jackh726 Nov 11, 2025
90ef10f
Fix test - updating items.fn.implicit-return to be more specific
jackh726 Nov 12, 2025
d582def
Fix CI
jackh726 Nov 12, 2025
eb381b7
Add section about uninhabited types in divergence.md
jackh726 Dec 3, 2025
e097e7e
Address Niko's comments.
jackh726 Dec 3, 2025
ebc51fd
Make `diverging_place_not_read` subject to failure
traviscross Dec 20, 2025
895c0dd
Consistently document and link diverging expressions
ehuss Jan 17, 2026
6475450
Fix misspelling in an example
ehuss Jan 17, 2026
d4a758c
Add note about use of an unstable nightly feature
ehuss Jan 17, 2026
635678b
Clarify subtlety about `if` example semicolon
ehuss Jan 17, 2026
6590a44
Fix stray `>`
ehuss Jan 17, 2026
876b727
Remove use of nightly feature in an example
ehuss Jan 17, 2026
a5923a2
Revert change to items.fn.implicit-return
ehuss Jan 17, 2026
7d95416
Clean up the links for expr.block.type.diverging
ehuss Jan 17, 2026
b48b527
Clarify the wording for expr.block.type
ehuss Jan 17, 2026
c8fbfa1
Clean up divergence chapter links
ehuss Jan 17, 2026
ae7499e
Rename rule expr.block.diverging
ehuss Jan 17, 2026
d4de939
Rename expr.match.conditional
ehuss Jan 17, 2026
4572f45
Consistently use periods in comments
ehuss Jan 17, 2026
0e40425
Rework the divergence intro
ehuss Jan 17, 2026
da671a8
Add back the list of divergence rules
ehuss Jan 17, 2026
87c2fc6
Add divergence.never
ehuss Jan 17, 2026
50def6f
Move propagation statement to a note
ehuss Jan 17, 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 @@ -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)

Expand Down
93 changes: 93 additions & 0 deletions src/divergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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:

- [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.

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 @@ -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.
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 @@ -63,6 +63,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 +98 to +100
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 @@ -292,13 +350,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 @@ -73,6 +73,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 @@ -180,4 +208,5 @@ r[expr.if.edition2024]

[`match` expressions]: match-expr.md
[boolean type]: ../types/boolean.md
[diverges]: divergence
[scrutinee]: ../glossary.md#scrutinee
10 changes: 9 additions & 1 deletion src/expressions/loop-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -292,6 +292,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.
Expand Down Expand Up @@ -355,6 +358,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 @@ -392,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
35 changes: 35 additions & 0 deletions src/expressions/match-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,39 @@ 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](../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 [`!`].

> [!EXAMPLE]
> ```rust
> # fn make<T>() -> T { loop {} }
> enum Empty {}
>
> fn diverging_match_no_arms() -> ! {
> let e: Empty = make();
> match e {}
> }
> ```


r[expr.match.diverging]
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 [`!`].
>
>```rust,compile_fail,E0004
> let a = match true {
> true => Some(panic!()),
> false => None,
> };
> // Fails to compile because `a` has the type `Option<!>`.
> match a {}
>```

r[expr.match.guard]
## Match guards

Expand Down Expand Up @@ -160,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
Expand Down
6 changes: 6 additions & 0 deletions src/expressions/return-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ 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.

r[expr.return.diverging]
A `return` expression is [diverging] and has a type of [`!`].

An example of a `return` expression:

```rust
Expand All @@ -22,3 +25,6 @@ fn max(a: i32, b: i32) -> i32 {
return b;
}
```

[`!`]: type.never
[diverging]: divergence