Skip to content

Commit e6e11d7

Browse files
committed
Write chapter on divergence
1 parent 76d5c46 commit e6e11d7

File tree

6 files changed

+87
-3
lines changed

6 files changed

+87
-3
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
- [Subtyping and variance](subtyping.md)
9898
- [Trait and lifetime bounds](trait-bounds.md)
9999
- [Type coercions](type-coercions.md)
100+
- [Divergence](divergence.md)
100101
- [Destructors](destructors.md)
101102
- [Lifetime elision](lifetime-elision.md)
102103

src/divergence.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
r[divergence]
2+
# Divergence
3+
4+
r[divergence.intro]
5+
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.
6+
7+
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!())`).
8+
9+
r[divergence.diverging-expressions]
10+
## Producing diverging expressions
11+
12+
r[divergence.diverging-expressions.unconditional]
13+
The following language constructs unconditonally produce a _diverging expression_ of the type [`!`](./types/never.md):
14+
15+
* [A call to a function returning `!`.](./types/never.md#r-type.never.constraint)
16+
* [A `loop` expression with no corresponding break.](./expressions/loop-expr.md#r-expr.loop.infinite.diverging)
17+
* [A `break` expression](./expressions/loop-expr.md#r-expr.loop.break.type)
18+
* [A `continue` expression](./expressions/loop-expr.md#r-expr.loop.continue.type)
19+
* [A `return` expression](./expressions/return-expr.md#r-expr.return.type)
20+
* [A `match` expression with no arms](./expressions/match-expr.md#r-expr.match.type.diverging.empty)
21+
* [A `block` expression that it itself is diverging.](../expressions/block-expr.md#r-expr.block.type.diverging)
22+
23+
r[divergence.diverging-expressions.conditional]
24+
In a control flow expression, if all arms diverge, then the entire expression also diverges.
25+
26+
r[divergence.fallback]
27+
## Fallback
28+
If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be `!`.
29+
30+
The following fails to compile because `!` does not implement `Debug`:
31+
```rust,compile_fail,E0277
32+
fn foo() -> i32 { 22 }
33+
match foo() {
34+
4 => Default::default(),
35+
_ => return,
36+
};
37+
```
38+
39+
> [!EDITION-2024]
40+
> Before the 2024 edition, the type was inferred to instead be `()`.
41+
42+
Importantly, type unification may happen *structurally*, so the fallback `!` may be part of a larger type. The following compiles:
43+
```rust
44+
fn foo() -> i32 { 22 }
45+
// This has the type `Option<!>`, not `!`
46+
match foo() {
47+
4 => Default::default(),
48+
_ => Some(return),
49+
};
50+
```
51+
52+
<!-- TODO: This last point should likely should be moved to a more general "type inference" section discussing generalization + unification. -->

src/expressions/block-expr.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ r[expr.block.result]
4444
Then the final operand is executed, if given.
4545

4646
r[expr.block.type]
47-
The type of a block is the type of the final operand, or `()` if the final operand is omitted.
47+
Typically, the type of a block is the type of the final operand, or `()` if the final operand is omitted.
4848

4949
```rust
5050
# fn fn_call() {}
@@ -60,8 +60,8 @@ let five: i32 = {
6060
assert_eq!(5, five);
6161
```
6262

63-
> [!NOTE]
64-
> 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.
63+
r[expr.block.type.diverging]
64+
However, if there are any values unconditionally created within a block that are [diverging](../divergence.md), then the block itself is considered diverging.
6565

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

src/expressions/loop-expr.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ Example:
308308
r[expr.loop.break.value]
309309
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`.
310310

311+
r[expr.loop.break.type]
312+
A `break` expression itself has a type of [`!`](../types/never.md).
313+
311314
r[expr.loop.block-labels]
312315
## Labelled block expressions
313316

@@ -367,6 +370,9 @@ Like `break`, `continue` is normally associated with the innermost enclosing loo
367370
r[expr.loop.continue.in-loop-only]
368371
A `continue` expression is only permitted in the body of a loop.
369372

373+
r[expr.loop.continue.type]
374+
A `continue` expression itself has a type of [`!`](../types/never.md).
375+
370376
r[expr.loop.break-value]
371377
## `break` and loop values
372378

src/expressions/match-expr.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,28 @@ Every binding in each `|` separated pattern must appear in all of the patterns i
9696
r[expr.match.binding-restriction]
9797
Every binding of the same name must have the same type, and have the same binding mode.
9898

99+
r[expr.match.type]
100+
The type of the overall `match` expression is the least upper bound of the individual match arms.
101+
102+
r[expr.match.type.diverging.empty]
103+
If there are no match arms, then the `match` expression is diverging and the type is [`!`](../types/never.md).
104+
105+
r[expr.match.type.diverging.conditional]
106+
If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges.
107+
108+
> [!NOTE]
109+
> If even the entire `match` expression diverges, its type may not be [`!`](../types/never.md).
110+
>
111+
>```rust,compile_fail,E0004
112+
> let a = match true {
113+
> true => Some(panic!()),
114+
> false => None,
115+
> };
116+
> // Fails to compile because `a` has the type `Option<!>`
117+
> // (or, `Option<()>` in edition 2021 and below)
118+
> match a {}
119+
>```
120+
99121
r[expr.match.guard]
100122
## Match guards
101123

src/expressions/return-expr.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ fn max(a: i32, b: i32) -> i32 {
2222
return b;
2323
}
2424
```
25+
26+
r[expr.return.type]
27+
A `return` expression itself has a type of [`!`](../types/never.md).

0 commit comments

Comments
 (0)