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)
Copy link
Contributor

@traviscross traviscross Oct 29, 2025

Choose a reason for hiding this comment

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

We try to keep the number of top-level chapters contained. Looking at it, perhaps most of what's here that can't be inlined on the pages for each expression would make sense appearing in the Expressions chapter (e.g., we talk about place and value expressions there -- the verbiage about when a place expression is diverging might make sense near that) and in the Never type chapter.

- [Destructors](destructors.md)
- [Lifetime elision](lifetime-elision.md)

Expand Down
37 changes: 37 additions & 0 deletions src/divergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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.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,
> };
> ```
Comment on lines +7 to +21
Copy link
Contributor

@traviscross traviscross Nov 2, 2025

Choose a reason for hiding this comment

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

It feels that we might need to say more here to guard against too simple a reading. We say, 1) not all diverging expressions have type ! (e.g. Some(panic!())), and 2) if a type to be inferred is only unified with diverging expressions, then the type will be inferred to be !.

However, of course, this does not compile:

trait Tr: Sized {
    fn m() -> Self { loop {} }
}
impl<T> Tr for T {}

fn f() -> u8 { 0 }
fn g() -> ! {
    match f() {
        0 => Tr::m(),
        //   ^^^^^^^ There's a type to be inferred here.
        _ => Some(panic!()),
        //   ^^^^^^^^^^^^^^ This is a diverging expression.
    } // ERROR: Mismatched types.
}

What might we be able to say to tease this apart?

> [!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. -->
44 changes: 43 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), the type of a block is the type of the final operand, or `()` if the final operand is omitted.

```rust
# fn fn_call() {}
Expand All @@ -63,6 +63,48 @@ 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#r-divergence.diverging-expressions), unless that expression is a place expression that is not read from.

```rust
# #![ feature(never_type) ]
# fn make<T>() -> T { loop {} }
let no_control_flow: ! = {
// There are no conditional statements, so this entire block is diverging.
loop {}
};
Comment on lines +70 to +75
Copy link
Contributor

Choose a reason for hiding this comment

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

You and I had talked about this. We had both thought, "maybe it's worth using the nightly never type to express this more clearly." I had mentioned I'd need to talk it over with @ehuss. In that discussion, @ehuss made a good point: why not just use functions? I.e., for the expression whose type we want to demonstrate, we can make that the trailing expression of a function that returns !. E.g.:

fn no_control_flow() -> ! {
    loop {}
}

That does seem likely the best approach. Sound right to you?


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;
};
};
Comment on lines +95 to +105
Copy link
Contributor

@traviscross traviscross Nov 2, 2025

Choose a reason for hiding this comment

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

This compiles and produces an infinite loop.

Playground link

But then, so does:

    let foo = Foo { x: make() };
    let diverging_place_not_read: () = {
        let _: () = {
            // Asssignment to something other than `_` means?
            let _x = foo.x;
        };
    };

Playground link

Is there a way to demonstrate this such that let _ = .. produces distinct compile-time or runtime behavior from let _x = ..?

Copy link
Contributor

@traviscross traviscross Nov 2, 2025

Choose a reason for hiding this comment

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

To answer the question posed, this compiles:

trait RetId { type Ty; }
impl<T> RetId for fn() -> T { type Ty = T; }

struct S<T> {
    x: T,
}

fn f(x: S<<fn() -> ! as RetId>::Ty>) -> ! {
    let _x = x.x; // OK.
}

But this does not:

fn f(x: S<<fn() -> ! as RetId>::Ty>) -> ! {
    let _ = x.x; // ERROR: Mismatched types.
}

This is one, though, where it's not immediately coming to mind how to express this without either the never type or the never type hack.

Copy link
Contributor

@traviscross traviscross Nov 2, 2025

Choose a reason for hiding this comment

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

Here's one way. (Of course, this is really just another instantiation of the never type hack.)

fn phantom_call<T>(_: impl FnOnce(T) -> T) {}

let _ = phantom_call(|x| -> ! {
    let _x = x; // OK.
});
let _ = phantom_call(|x| -> ! {
    let _ = x; // ERROR: Mismatched types.
});

Copy link
Contributor

@traviscross traviscross Nov 2, 2025

Choose a reason for hiding this comment

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

It's interesting how it really does need the ! type to be ascribed for this to work. I.e., it doesn't work with:

struct W<T>(T);

let x = W(loop {});
let _ = || -> ! {
    let _x = x.0; // ERROR.
};

Any thoughts about the reason for that?

```

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

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

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
21 changes: 21 additions & 0 deletions src/expressions/match-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ 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.
Comment on lines +99 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.

If we're inlining the rule about determining the type based on the LUB for match, from https://doc.rust-lang.org/1.90.0/reference/type-coercions.html#r-coerce.least-upper-bound.intro, probably we'd need to do that for the other rules there also (and then either remove the list from there or convert it to an admonition or index).

Copy link
Contributor

Choose a reason for hiding this comment

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

As an aside, looking into this rule is what prompted me to file:


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