Skip to content

Add more rules about when expressions diverge - #2186

Open
ehuss wants to merge 1 commit into
rust-lang:masterfrom
ehuss:more-divergence
Open

Add more rules about when expressions diverge#2186
ehuss wants to merge 1 commit into
rust-lang:masterfrom
ehuss:more-divergence

Conversation

@ehuss

@ehussehuss commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

#2067 added a chapter on divergence along with rules for some of the more subtle expressions like if or match. This adds rules for almost all the rest of the expressions.

Most of these are pretty straightforward propagation rules which could be left implicit. It may seem a little excessive to have a separate rule for each one, but I think the consistency is worth it because there are various subtleties. This also may help us be clear about these things when adding new expressions to the language to ensure we think about the divergence rules.

This does not cover const expressions (const blocks, static, const, array repeat, etc.) because I still do not yet fully know how those should be documented (see #2153).

I'm not entirely excited by having the long list of rules in the divergence chapter, mostly because of the length. However, I think it is helpful to cross-index these kinds of things.

There are some interesting subtleties that I noticed:

  • while loops cannot diverge. It's a little surprising to me (I would expect the condition to allow it to diverge). I suspect this is due to the desugaring to a loop expression using break, which does not diverge.
  • Updated details for let-chains.

References for the implementation:

Closes#2152

@rustbotrustbot added the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label Feb 20, 2026
@ehuss

Copy link
Copy Markdown
ContributorAuthor

@jackh726 Would you be able to review this?

@jackh726jackh726 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems good to me. Definitely the expression list in divergence.md is pretty long now.

> The future type that rustc generates is roughly equivalent to an enum with one variant per `await` point, where each variant stores the data needed to resume from its corresponding point.

r[expr.block.async.diverging]
An async block expression does not itself [diverge], but evaluating the future (such as through `await`) diverges if the output type is the [never type].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The latter half is covered by expr.await.diverging. Is the duplication worth it?

@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

Comment threadsrc/expressions/array-expr.md Outdated
Comment threadsrc/inline-assembly.md Outdated
## Divergence

r[asm.diverging.noreturn]
The `asm!` macro [diverges] if it uses the [`noreturn`] option.

@traviscrosstraviscrossJun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
The `asm!` macro [diverges] if it uses the [`noreturn`] option.
The `asm!` macro [diverges] if it uses the [`noreturn`] option and no `label` block returns unit.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Our rule says:

r[asm.options.supported-options.noreturn]

  • noreturn: The assembly code does not fall through; behavior is undefined if it does. It may still jump to label blocks. If any label blocks return unit, the asm! block will return unit. Otherwise it will return ! (never). As with a call to a function that does not return, local variables in scope are not dropped before execution of the assembly code.

Interestingly, it doesn't actually say whether or not it diverges. But, logically, it should not, and testing bears this out.

The noreturn option is misnamed now that we've stabilized labeled blocks. It should really be nofallthrough or similar — it's asserting only that we don't fall through the assembly code. If we jump from that code to a labeled block, that labeled block can return normally without diverging.

Comment threadsrc/expressions/path-expr.md Outdated
Comment on lines +37 to +44
r[expr.path.diverging]
A path expression does not [diverge].

<!--
TODO: When stabilizing never type, this changes to:

A path expression [diverges] if the path resolves to a value with the [never type] and it is a place expression that is guaranteed to constitute a read.
-->

@traviscrosstraviscrossJun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's actually possible to observe this on stable Rust. E.g.:

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

Let's also adjust this language to use the "value is guaranteed to be read" phrasing used elsewhere.

View changes since the review

@rustbot

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

rust-lang#2067 added a chapter on
divergence along with rules for some of the more subtle expressions like
`if` or `match`. This adds rules for almost all the rest of the
expressions.
Most of these are pretty straightforward propagation rules which could
be left implicit. It may seem a little excessive to have a separate rule
for each one, but I think the consistency is worth it because there are
various subtleties. This also may help us be clear about these things
when adding new expressions to the language to ensure we think about the
divergence rules.
This does not cover const expressions (const blocks, static, const,
array repeat, etc.) because I still do not yet fully know how those
should be documented (see
rust-lang#2153).
I'm not entirely excited by having the long list of rules in the
divergence chapter, mostly because of the length. However, I think it is
helpful to cross-index these kinds of things.
There are some interesting subtleties that I noticed:
- `while` loops cannot diverge. It's a little surprising to me (I would
expect the condition to allow it to diverge). I suspect this is due to
the desugaring to a loop expression using `break`, which does not
diverge.
- Updated details for let-chains.
References for the implementation:
- Place expression with never must be read: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L318-L326
- `break` is never: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L819-L820
- `continue` is never: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/expr.rs#L861
- `return` is never: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/expr.rs#L921
- `if` divergence: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L1236-L1242
- `loop`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L1450-L1456
- `asm!`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L3684
- `match`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/_match.rs#L19-L178
- block break: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs#L1167-L1171
- lazy bool: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/op.rs#L111Closesrust-lang#2152
@ehuss

Copy link
Copy Markdown
ContributorAuthor

(For the record, I am finding more mistakes here and am working on them.)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-reviewStatus: The marked PR is awaiting review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detail divergence for the rest of the expressions

4 participants

@ehuss@rustbot@traviscross@jackh726