Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 56 additions & 0 deletions src/divergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the following a correct definition of a diverging expression?

A *diverging expression* is an expression that never completes normal execution.

And then I would expect a rule below to explain which expressions are diverging. That can just be a list linking to the other relevant rules in their respective chapters. However, reading through this, I'm unable to come up a clear definition of what is diverging. Is it possible to come up with a list of what is 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.

That is imo correct, and close to what I originally had before I changed it based on Niko's review.

I did originally have a list, which TC asked me to remove: #2067 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

(though, that list is not entirely accurate, I've since realized, because a block expression can be diverging but not have a type of ! - so, accurate as written, but not an "exhaustive" list of diverging expressions, just diverging expressions producing !)


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>() -> 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::<Empty>();
> }
> ```
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. -->
47 changes: 46 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.
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

This gives an exception for divergence, but doesn't explicitly say that the type of the block is the never type if the block diverges. I'm wondering if something like the following might clear that up?

Suggested change
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], in which case it is the [never type].

I'm guessing this logic comes from somewhere in check_expr_block.

However, there's some things in there about may_break I don't quite follow.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like your wording, but yeah, this is a good point about break:

This fails because the type of the block is usize:

    fn block_type_diverging_break() -> ! {
        let x = 'block: {
            break 'block 0usize;
            loop {};
        };
        x
    }

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 !).


```rust
# fn fn_call() {}
Expand All @@ -63,6 +63,51 @@ 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]
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>() -> T { loop {} }
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 {}
}
}

struct Foo {
x: !,
}
Comment on lines +95 to +97
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 diverging_place_read() -> ! {
let foo = Foo { x: make() };
// A read of a place expression produces a diverging block
let _x = foo.x;
}
fn diverging_place_not_read() -> () {
let foo = Foo { x: make() };
// 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.

Expand Down
25 changes: 25 additions & 0 deletions src/expressions/if-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ 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` statement is `()`, despite being diverging.
Comment on lines +87 to +88
Copy link
Contributor

Choose a reason for hiding this comment

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

This is pretty subtle because you need to understand the rules for how an elided final expression works. I'm wondering if a little extra commentary could help here.

Suggested change
// 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 `!`.

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 is accurate.

}
fn diverging_arms() -> ! {
// Diverges because all arms diverge
if true {
loop {}
} else {
loop {}
}
}
```

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

Expand Down
4 changes: 4 additions & 0 deletions src/expressions/loop-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -355,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.

Expand Down
33 changes: 33 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 [`!`](../types/never.md).

> [!EXAMPLE]
> ```rust
> # fn make<T>() -> T { loop {} }
> enum Empty {}
>
> fn diverging_match_no_arms() -> ! {
> let e: Empty = make();
> 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.

> [!NOTE]
> 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<!>`.
> match a {}
>```

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

Expand Down
2 changes: 2 additions & 0 deletions src/expressions/return-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/items/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Copy link
Contributor

@traviscross traviscross Dec 18, 2025

Choose a reason for hiding this comment

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

In talking over this PR in the office hours, @ehuss and I puzzled over this rule. Presumably the not is misplaced, but even with that removed, we weren't clear on how to assign meaning to this given, of course:

fn f() {
    loop {} // Diverging expression.
}

let _ = || -> ! { f() }; //~ ERROR mismatched types
let _: fn() -> ! = f; //~ ERROR mismatched types

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, this is a good catch. Indeed, the additional language is not correct for the function signature.

What I had in mind when writing this was that when type checking the function body the implicit return is ! if it is diverging. This is technically covered by my changes to expr.block.type.diverging, so I'm not sure why I thought that these changes were also needed.

I think it was motivated by this example. I guess, maybe there is an equivalent statement missing about the implicit "final type" of a block (which is by default () and otherwise ! when diverging).

Copy link
Contributor

Choose a reason for hiding this comment

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

Should this change be reverted, then?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, probably. But I wonder if there should be some additional text added somewhere that the type of the function block may not actually be the return type of the function, but rather must be coercible into the return type of the function.


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.
Expand Down