From 323d51599cd75d8a0442468b8068f4ae7e676bc0 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 29 Sep 2026 14:23:02 -0700 Subject: [PATCH 1/4] Add more rules about when expressions diverge https://github.com/rust-lang/reference/pull/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 https://github.com/rust-lang/reference/issues/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#L111 Closes https://github.com/rust-lang/reference/issues/2152 --- dev-guide/src/rules/index.md | 1 + src/divergence.md | 33 ++++- src/expressions/array-expr.md | 45 +++++++ src/expressions/await-expr.md | 12 ++ src/expressions/block-expr.md | 22 ++++ src/expressions/call-expr.md | 33 +++++ src/expressions/closure-expr.md | 22 ++++ src/expressions/field-expr.md | 42 +++++++ src/expressions/grouped-expr.md | 11 ++ src/expressions/if-expr.md | 34 ++++++ src/expressions/literal-expr.md | 4 + src/expressions/loop-expr.md | 166 ++++++++++++++++++------- src/expressions/match-expr.md | 63 ++++++++-- src/expressions/method-call-expr.md | 39 ++++++ src/expressions/operator-expr.md | 180 ++++++++++++++++++++++++++++ src/expressions/path-expr.md | 25 +++- src/expressions/range-expr.md | 17 +++ src/expressions/return-expr.md | 12 ++ src/expressions/struct-expr.md | 20 ++++ src/expressions/tuple-expr.md | 21 ++++ src/expressions/underscore-expr.md | 13 ++ src/inline-assembly.md | 47 ++++++++ src/statements.md | 19 +++ 23 files changed, 829 insertions(+), 52 deletions(-) diff --git a/dev-guide/src/rules/index.md b/dev-guide/src/rules/index.md index e19d10bf30..829b284ec5 100644 --- a/dev-guide/src/rules/index.md +++ b/dev-guide/src/rules/index.md @@ -33,6 +33,7 @@ When assigning rules to new paragraphs or modifying rule names, use the followin - `intro`: The beginning paragraph of each section. It should explain the construct being defined overall. - `syntax`: Syntax definitions or explanations when BNF syntax definitions are not used. - `namespace`: For items only, specifies the namespace(s) the item introduces a name in. It may also be used elsewhere when defining a namespace (e.g., `r[attribute.diagnostic.namespace]`). + - `diverging`: The divergence behavior of an expression. Be sure to update the Divergence chapter, too. 6. When a rule doesn't fall under the above keywords, or for section rule IDs, name the subrule as follows: - If the rule names a specific Rust language construct (e.g., an attribute, standard library type/function, or keyword-introduced concept), use the construct as named in the language, appropriately case-adjusted (but do not replace `_`s with `-`s). - Other than Rust language concepts with `_`s in the name, use `-` characters to separate words within a "subrule". diff --git a/src/divergence.md b/src/divergence.md index ab6ba0ab8b..3a57920b99 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -17,16 +17,47 @@ fn example() { See the following rules for specific expression divergence behavior: +- [asm.diverging.naked_asm] --- `naked_asm!`. +- [asm.diverging.noreturn] --- `noreturn` in `asm!`. +- [expr.arith-logic.diverging] --- Arithmetic and logic expressions. +- [expr.array.diverging] --- Array expressions. +- [expr.array.index.diverging] --- Index expressions. +- [expr.as.diverging] --- `as` expressions. +- [expr.assign.diverging] --- Assignment expressions. +- [expr.await.diverging] --- `.await` expressions. +- [expr.block.async.diverging] --- Async block expressions. - [expr.block.diverging] --- Block expressions. +- [expr.bool-logic.diverging] --- Lazy boolean expressions. +- [expr.borrow.diverging] --- Borrow expressions. +- [expr.call.diverging] --- Call expressions. +- [expr.closure.diverging] --- Closure expressions. +- [expr.cmp.diverging] --- Comparison expressions. +- [expr.compound-assign.diverging] --- Compound assignment expressions. +- [expr.deref.diverging] --- Dereference expressions. +- [expr.field.diverging] --- Field expressions. - [expr.if.diverging] --- `if` expressions. +- [expr.literal.diverging] --- Literal expressions. - [expr.loop.block-labels.type] --- Labeled block expressions with `break`. - [expr.loop.break-value.diverging] --- `loop` expressions with `break`. - [expr.loop.break.diverging] --- `break` expressions. - [expr.loop.continue.diverging] --- `continue` expressions. +- [expr.loop.for.diverging] --- `for` expressions. - [expr.loop.infinite.diverging] --- Infinite `loop` expressions. -- [expr.match.diverging] --- `match` expressions. +- [expr.loop.while.diverging] --- `while` expressions. +- [expr.match.arms.diverging] and [expr.match.scrutinee.diverging] --- `match` expressions. - [expr.match.empty] --- Empty `match` expressions. +- [expr.method.diverging] --- Method call expressions. +- [expr.negate.diverging] --- Negation expressions. +- [expr.paren.diverging] --- Parenthesized expressions. +- [expr.path.diverging] --- Path expressions. +- [expr.placeholder.diverging] --- Underscore expressions. +- [expr.range.diverging] --- Range expressions. - [expr.return.diverging] --- `return` expressions. +- [expr.struct.diverging] --- Struct expressions. +- [expr.try.diverging] --- Try propagation expressions. +- [expr.tuple-index.diverging] --- Tuple indexing expressions. +- [expr.tuple.diverging] --- Tuple expressions. +- [statement.let.diverging] --- `let` statements. > [!NOTE] > The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging. diff --git a/src/expressions/array-expr.md b/src/expressions/array-expr.md index d0fe247635..928bae3d2c 100644 --- a/src/expressions/array-expr.md +++ b/src/expressions/array-expr.md @@ -72,6 +72,16 @@ const EMPTY: Vec = Vec::new(); [EMPTY; 2]; ``` +r[expr.array.diverging] +An array expression [diverges] if any of its operands diverges. + +```rust +fn array_diverges(x: !) -> ! { + // OK, expression diverges. + [x]; +} +``` + r[expr.array.index] ## Array and slice indexing expressions @@ -128,6 +138,39 @@ arr[10]; // warning: index out of bounds r[expr.array.index.trait-impl] The array index expression can be implemented for types other than arrays and slices by implementing the [Index] and [IndexMut] traits. +r[expr.array.index.diverging] +An index expression [diverges] if either of its operands diverges. + +```rust +fn index_base_diverges(a: [i32; 1]) -> ! { + // OK, expression diverges. + ({ loop {}; a })[0]; +} + +fn index_diverges(a: [i32; 1]) -> ! { + // OK, expression diverges. + a[{ loop {}; 0 }]; +} +``` + +> [!NOTE] +> An index expression also diverges if the type of the indexed element is the [never type] and the value is guaranteed to be read (see [divergence.never]). +> +> ```rust +> fn diverging_place_read(x: [!; 1]) -> ! { +> // A read of a place expression produces a diverging block. +> let a = x[0]; +> } +> ``` +> +> ```rust,compile_fail,E0308 +> fn diverging_place_no_read(x: [!; 1]) -> ! { +> // This does not constitute a read, and thus does not diverge. +> let _ = x[0]; +> // ERROR: Expected type !, found () +> } +> ``` + [`Copy`]: ../special-types-and-traits.md#copy [IndexMut]: std::ops::IndexMut [Index]: std::ops::Index @@ -136,9 +179,11 @@ The array index expression can be implemented for types other than arrays and sl [const block expression]: expr.block.const [constant expression]: ../const_eval.md#constant-expressions [constant item]: ../items/constant-items.md +[diverges]: divergence [inferred const]: items.generics.const.inferred [literal]: ../tokens.md#literals [memory location]: ../expressions.md#place-expressions-and-value-expressions +[never type]: type.never [panic]: ../panic.md [path]: path-expr.md [slice]: ../types/slice.md diff --git a/src/expressions/await-expr.md b/src/expressions/await-expr.md index ee6d90f031..5cdf9b6cba 100644 --- a/src/expressions/await-expr.md +++ b/src/expressions/await-expr.md @@ -29,6 +29,16 @@ r[expr.await.edition2018] > [!EDITION-2018] > Await expressions are only available beginning with Rust 2018. +r[expr.await.diverging] +An await expression [diverges] if the future's output type is the [never type]. + +```rust +async fn await_diverge(x: impl Future) -> ! { + // OK, expression diverges. + x.await; +} +``` + r[expr.await.task] ## Task context @@ -63,6 +73,8 @@ where the `yield` pseudo-code returns `Poll::Pending` and, when re-invoked, resu [`poll::Pending`]: std::task::Poll::Pending [`poll::Ready`]: std::task::Poll::Ready [async context]: ../expressions/block-expr.md#async-context +[diverges]: divergence [future]: std::future::Future +[never type]: type.never [`IntoFuture`]: std::future::IntoFuture [`IntoFuture::into_future`]: std::future::IntoFuture::into_future diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 8d0cf077dd..9c54850e93 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -178,6 +178,27 @@ The actual data format for this type is unspecified. > [!NOTE] > 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 [diverge]. + +```rust,compile_fail,E0308 +async fn async_block_does_not_diverge() -> ! { + async { loop {} }; + // ERROR: Expected type !, found () +} +``` + +> [!NOTE] +> Evaluating the future such that the output type is the [never type] will result in a value whose type is the never type, and per [divergence.never] the expression diverges. +> +> ```rust +> async fn async_block_await_diverge() -> ! { +> // This explicitly specifies the type to be the never type because +> // otherwise the type inference would infer the output to be unit. +> let x: ! = async { loop {} }.await; +> } +> ``` + r[expr.block.async.edition2018] > [!EDITION-2018] > Async blocks are only available beginning with Rust 2018. @@ -339,6 +360,7 @@ fn is_unix_platform() -> bool { [call expressions]: call-expr.md [capture modes]: ../types/closure.md#capture-modes [constant items]: ../items/constant-items.md +[diverge]: divergence [diverges]: expr.block.diverging [final operand]: expr.block.inner-attributes [free item]: ../glossary.md#free-item diff --git a/src/expressions/call-expr.md b/src/expressions/call-expr.md index 09b8aeacc8..a02c34e743 100644 --- a/src/expressions/call-expr.md +++ b/src/expressions/call-expr.md @@ -32,6 +32,37 @@ let three: i32 = add(1i32, 2i32); let name: &'static str = (|| "Rust")(); ``` +r[expr.call.diverging] +A call expression [diverges] if any of its operands diverges. + +```rust +fn callee_diverges(x: !) -> ! { + // OK, expression diverges. + {x}(); +} + +fn takes_argument(_: i32) {} + +fn argument_diverges(x: !) -> ! { + // OK, expression diverges. + takes_argument(x); +} +``` + +> [!NOTE] +> If the return type of the called function is the [never type], then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> +> ```rust +> fn exit() -> ! { +> loop {} +> } +> +> fn return_value_is_never() -> ! { +> // OK, expression diverges. +> exit(); +> } +> ``` + r[expr.call.desugar] ## Disambiguating function calls @@ -103,5 +134,7 @@ Refer to [RFC 132] for further details and motivations. [`default()`]: std::default::Default::default [`size_of()`]: std::mem::size_of [automatically dereferenced]: field-expr.md#automatic-dereferencing +[diverges]: divergence [fully-qualified syntax]: ../paths.md#qualified-paths +[never type]: type.never [non-function types]: ../types/function-item.md diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index ac9a69da4a..4ed1121f06 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -31,6 +31,26 @@ A closure expression denotes a function that maps a list of parameters onto the r[expr.closure.unique-type] Each closure expression has a unique, anonymous type. +r[expr.closure.diverging] +A closure expression does not [diverge]. + +```rust,compile_fail,E0308 +fn closure_does_not_diverge() -> ! { + || -> ! { loop {} }; + // ERROR: Expected type !, found () +} +``` + +> [!NOTE] +> If the return type of the closure is the [never type], and the closure is called, then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> +> ```rust +> fn closure_call_diverge() -> ! { +> // OK, call expression diverges. +> || -> ! { loop {} }(); +> } +> ``` + r[expr.closure.captures] Significantly, closure expressions _capture their environment_, which regular [function definitions] do not. @@ -105,6 +125,8 @@ Attributes on closure parameters follow the same rules and restrictions as [regu [block]: block-expr.md [call traits and coercions]: ../types/closure.md#call-traits-and-coercions [closure type]: ../types/closure.md +[diverge]: divergence [function definitions]: ../items/functions.md +[never type]: type.never [patterns]: ../patterns.md [regular function parameters]: ../items/functions.md#attributes-on-function-parameters diff --git a/src/expressions/field-expr.md b/src/expressions/field-expr.md index 2073979610..12b0e62b95 100644 --- a/src/expressions/field-expr.md +++ b/src/expressions/field-expr.md @@ -42,6 +42,46 @@ foo().x; (mystruct.function_field)() // Call expression containing a field expression ``` +r[expr.field.diverging] +A field expression [diverges] if its expression operand diverges. + +```rust +struct S { + f1: i32 +} + +fn field_diverge(x: !) -> ! { + // OK, expression diverges. + (x as S).f1; +} +``` + +> [!NOTE] +> A field expression also diverges if the type of the field is the [never type] and the value is guaranteed to be read (see [divergence.never]). +> +> ```rust +> struct S { +> f: T +> } +> +> fn diverging_place_read(x: S) -> ! { +> // OK: A read of a place expression diverges. +> let a = x.f; +> } +> ``` +> +> ```rust,compile_fail,E0308 +> # struct S { +> # f: T +> # } +> # +> fn diverging_place_no_read(x: S) -> ! { +> // This does not constitute a read. +> let _ = x.f; +> // ERROR: Expected type !, found () +> } +> ``` + r[expr.field.autoref-deref] ## Automatic dereferencing @@ -71,8 +111,10 @@ let d: String = x.f3; // Move out of x.f3 [`drop`]: ../special-types-and-traits.md#drop [identifier]: ../identifiers.md [call expression]: call-expr.md +[diverges]: divergence [method call expression]: method-call-expr.md [mutable]: ../expressions.md#mutability +[never type]: type.never [parenthesized expression]: grouped-expr.md [place expression]: ../expressions.md#place-expressions-and-value-expressions [struct]: ../items/structs.md diff --git a/src/expressions/grouped-expr.md b/src/expressions/grouped-expr.md index 20f2890b1c..3f03568a46 100644 --- a/src/expressions/grouped-expr.md +++ b/src/expressions/grouped-expr.md @@ -44,4 +44,15 @@ assert_eq!( a.f (), "The method f"); assert_eq!((a.f)(), "The field f"); ``` +r[expr.paren.diverging] +A parenthesized expression [diverges] if its operand diverges. + +```rust +fn parenthesized_diverge(x: !) -> ! { + // OK, expression diverges. + (x); +} +``` + +[diverges]: divergence [place]: ../expressions.md#place-expressions-and-value-expressions diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index 70314c3659..6dd072bafd 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -98,6 +98,23 @@ fn diverging_arms() -> ! { } ``` +r[expr.if.chains.diverging] +The condition expression diverges if the leftmost condition in the `&&` chain diverges. + +```rust +fn if_cond_chain_lhs_diverge(x: !) -> ! { + // OK, expression diverges. + if x && false {}; +} +``` + +```rust,compile_fail,E0308 +fn if_cond_chain_rhs_diverge(x: !) -> ! { + if false && x {}; + // ERROR: Expected type !, found () +} +``` + r[expr.if.let] ## `if let` patterns @@ -142,6 +159,23 @@ if let E::X(n) | E::Y(n) = v { } ``` +r[expr.if.let.diverging] +A `let` pattern causes the condition to diverge if the initializer diverges unless the initializer is a place that is not read. + +```rust +fn if_let_diverging(x: !) -> ! { + // OK: The let pattern is read causing this to diverge. + if let a = x {}; +} +``` + +```rust,compile_fail,E0308 +fn if_let_diverging_not_read(x: !) -> ! { + if let _ = x {}; + // ERROR: expected `!`, found `()` +} +``` + r[expr.if.chains] ## Chains of conditions diff --git a/src/expressions/literal-expr.md b/src/expressions/literal-expr.md index 2b987c5c21..6d174414c4 100644 --- a/src/expressions/literal-expr.md +++ b/src/expressions/literal-expr.md @@ -24,6 +24,9 @@ A _literal expression_ is an expression consisting of a single token, rather tha r[expr.literal.const-expr] A literal is a form of [constant expression], so is evaluated (primarily) at compile time. +r[expr.literal.diverging] +A literal expression does not [diverge]. + r[expr.literal.literal-token] Each of the lexical [literal][literal tokens] forms described earlier can make up a literal expression, as can the keywords `true` and `false`. @@ -505,6 +508,7 @@ The expression's type is the primitive [boolean type], and its value is: [boolean type]: ../types/boolean.md [constant expression]: ../const_eval.md#constant-expressions [CStr]: core::ffi::CStr +[diverge]: divergence [floating-point types]: ../types/numeric.md#floating-point-types [lint check]: ../attributes/diagnostics.md#lint-check-attributes [literal tokens]: ../tokens.md#literals diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md index 8e67295482..40b6cc1bbe 100644 --- a/src/expressions/loop-expr.md +++ b/src/expressions/loop-expr.md @@ -43,6 +43,21 @@ A `loop` expression repeats execution of its body continuously: `loop { println! r[expr.loop.infinite.diverging] A `loop` expression without an associated `break` expression is [diverging] and has type [`!`]. +```rust +fn loop_diverges() -> ! { + // OK, expression diverges. + loop {}; +} +``` + +```rust,compile_fail,E0308 +fn loop_with_break_does_not_diverge() -> ! { + // This expression does not diverge, thus the body does not diverge. + loop { break; }; + // ERROR: Expected type !, found () +} +``` + 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). @@ -80,6 +95,23 @@ while i < 10 { } ``` +r[expr.loop.while.diverging] +A `while` loop does not [diverge]. + +```rust,compile_fail,E0308 +fn while_diverge_condition(x: !) -> ! { + while x {}; + // ERROR: Expected type !, found () +} +``` + +```rust,compile_fail,E0308 +fn while_diverge_block(x: !) -> ! { + while true { x }; + // ERROR: Expected type !, found () +} +``` + r[expr.loop.while.let] ### `while let` patterns @@ -228,6 +260,35 @@ The variable names `next`, `iter`, and `val` are for exposition only, they do no > [!NOTE] > The outer `match` is used to ensure that any [temporary values] in `iter_expr` don't get dropped before the loop is finished. `next` is declared before being assigned because it results in types being inferred correctly more often. +r[expr.loop.for.diverging] +A `for` loop [diverges] if the iterator expression diverges. + +```rust +fn for_iterator_diverges(x: !) -> ! { + // OK, expression diverges. + for _ in x as &[i32] {}; +} +``` + +```rust,compile_fail,E0308 +fn for_body_does_not_diverge() -> ! { + for _ in [1, 2, 3] { + // Diverging expressions in the loop body does not constitute as + // divergence in the loop itself. + loop {} + }; + // ERROR: Expected type !, found () +} +``` + +```rust,compile_fail,E0308 +fn iterator_returns_never_does_not_diverge(x: [!; 1]) -> ! { + // An iterator that yields a never type does not diverge. + for a in x {}; + // ERROR: Expected type !, found () +} +``` + r[expr.loop.label] ## Loop labels @@ -282,6 +343,15 @@ assert_eq!(last, 12); r[expr.loop.break.diverging] A `break` expression is [diverging] and has a type of [`!`]. +```rust +fn break_diverges() { + for _ in [1, 2, 3] { + // The type of `break` is `!`. + let _: ! = break; + }; +} +``` + 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. Example: @@ -338,19 +408,26 @@ 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 -> }; -> } -> ``` +```rust +fn labeled_block_lub(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 + }; +} + +fn labeled_block_diverges(x: !) -> ! { + // OK, expression diverges. + let _: ! = 'block: { + break 'block x; + // Final operand is inferred to the never type. + }; +} +``` r[expr.loop.continue] ## `continue` expressions @@ -366,6 +443,15 @@ When `continue` is encountered, the current iteration of the associated loop bod r[expr.loop.continue.diverging] A `continue` expression is [diverging] and has a type of [`!`]. +```rust +fn continue_diverges() { + loop { + // The type of `continue` is `!`. + let _: ! = continue; + } +} +``` + r[expr.loop.continue.while] In the case of a `while` loop, the head is the conditional operands controlling the loop. @@ -418,33 +504,32 @@ The type of a `loop` with associated `break` expressions is the [least upper bou r[expr.loop.break-value.diverging] A `loop` with associated `break` expressions does not [diverge] if any of the break operands do not diverge. If all of the `break` operands diverge, then the `loop` expression also diverges. -> [!EXAMPLE] -> ```rust -> fn diverging_loop_with_break(condition: bool) -> ! { -> // This loop is diverging because all `break` operands are diverging. -> loop { -> if condition { -> break loop {}; -> } else { -> break panic!(); -> } -> } -> } -> ``` -> -> ```rust,compile_fail,E0308 -> fn loop_with_non_diverging_break(condition: bool) -> ! { -> // The type of this loop is i32 even though one of the breaks is -> // diverging. -> loop { -> if condition { -> break loop {}; -> } else { -> break 123i32; -> } -> } // ERROR: expected `!`, found `i32` -> } -> ``` +```rust +fn diverging_loop_with_break(condition: bool) -> ! { + // This loop is diverging because all `break` operands are diverging. + loop { + if condition { + break loop {}; + } else { + break panic!(); + } + } +} +``` + +```rust,compile_fail,E0308 +fn loop_with_non_diverging_break(condition: bool) -> ! { + // The type of this loop is i32 even though one of the breaks is + // diverging. + loop { + if condition { + break loop {}; + } else { + break 123i32; + } + } // ERROR: expected `!`, found `i32` +} +``` [`!`]: type.never [`if` condition chains]: if-expr.md#chains-of-conditions @@ -452,6 +537,7 @@ A `loop` with associated `break` expressions does not [diverge] if any of the br [`match` expression]: match-expr.md [boolean type]: ../types/boolean.md [diverge]: divergence +[diverges]: divergence [diverging]: divergence [labeled block expression]: expr.loop.block-labels [least upper bound]: coerce.least-upper-bound diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index 2cbe38eab6..e40682a7d0 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -117,18 +117,61 @@ The type of the overall `match` expression is the [least upper bound] of the ind r[expr.match.empty] If there are no match arms, then the `match` expression is [diverging] and the type is [`!`]. -> [!EXAMPLE] -> ```rust -> enum Empty {} -> -> fn diverging_match_no_arms(e: Empty) -> ! { -> match e {} -> } -> ``` +```rust +enum Empty {} +fn diverging_match_no_arms(e: Empty) -> ! { + // OK, expression diverges. + match e {}; +} +``` + +r[expr.match.arms.diverging] +A `match` expression diverges if all of the match arms diverge. -r[expr.match.diverging] -If either the scrutinee expression or all of the match arms diverge, then the entire `match` expression also diverges. +```rust +fn match_all_arms_diverge(x: i32) -> ! { + // OK: All arms diverge, thus the match diverges. + match x { + 1 => loop {}, + _ => loop {}, + }; +} +``` + +```rust,compile_fail,E0308 +fn match_some_arms_diverge(x: i32) -> ! { + // Not all arms diverge, thus the match does not diverge. + match x { + 1 => loop {}, + _ => (), + }; + // ERROR: expected `!`, found `()` +} +``` + +r[expr.match.scrutinee.diverging] +A `match` expression diverges if the scrutinee diverges unless the scrutinee is a place expression and not every arm's pattern constitutes a read of that place. + +```rust +fn match_scrutinee_diverges(x: !) -> ! { + // OK: Scrutinee diverges, and it is read, thus the match diverges. + match x { + a => () + }; +} +``` + +```rust,compile_fail,E0308 +fn match_scrutinee_diverges_not_read(x: !) -> ! { + // Diverging scrutinee is not read in all arms, thus the match does not diverge. + match x { + value => (), + _ => (), + }; + // ERROR: expected `!`, found `()` +} +``` r[expr.match.guard] ## Match guards diff --git a/src/expressions/method-call-expr.md b/src/expressions/method-call-expr.md index 02e96b6511..2911d356fe 100644 --- a/src/expressions/method-call-expr.md +++ b/src/expressions/method-call-expr.md @@ -83,12 +83,51 @@ r[expr.method.edition2021] > [!WARNING] > For [trait objects], if there is an inherent method of the same name as a trait method, it will give a compiler error when trying to call the method in a method call expression. Instead, you can call the method using [disambiguating function call syntax], in which case it calls the trait method, not the inherent method. There is no way to call the inherent method. Just don't define inherent methods on trait objects with the same name as a trait method and you'll be fine. +r[expr.method.diverging] +A method call expression [diverges] if any of its operands diverges. + +```rust +struct S; + +impl S { + fn f(&self, a: i32) {} +} + +fn callee_diverges(x: !) -> ! { + // OK, expression diverges. + (x as S).f(123); +} + +fn argument_diverges(x: !) -> ! { + // OK, expression diverges. + S.f(x); +} +``` + +> [!NOTE] +> If the return type of the called method is the [never type], then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> +> ```rust +> struct S; +> +> impl S { +> fn returns_never(&self) -> ! { loop {} } +> } +> +> fn return_value_is_never() -> ! { +> // OK, expression diverges. +> S.returns_never(); +> } +> ``` + [visible]: ../visibility-and-privacy.md [array type]: ../types/array.md [trait objects]: ../types/trait-object.md [disambiguate call]: call-expr.md#disambiguating-function-calls [disambiguating function call syntax]: call-expr.md#disambiguating-function-calls [dereference]: operator-expr.md#the-dereference-operator +[diverges]: divergence [methods]: ../items/associated-items.md#methods +[never type]: type.never [unsized coercion]: ../type-coercions.md#unsized-coercions [`IntoIterator`]: std::iter::IntoIterator diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index c692122bf4..287e15d24e 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -105,6 +105,29 @@ let a = && && mut 10; let a = & & & & mut 10; ``` +r[expr.borrow.diverging] +A borrow expression [diverges] if its operand diverges unless the operand is a [place expression]. + +```rust +fn borrow_with_diverging_expression() -> ! { + // OK: Operand diverges and is not a place, thus this statement also diverges. + &{ loop {} }; +} + +fn raw_borrow_diverges() -> ! { + // OK, expression diverges. + &raw const *{ loop {}; &() }; +} +``` + +```rust,compile_fail,E0308 +fn borrow_operand_with_never_type(x: !) -> ! { + // An operand of a place with the never type is not considered to diverge. + &x; + // ERROR: expected `!`, found `()` +} +``` + r[expr.borrow.raw] ### Raw borrow operators @@ -204,6 +227,34 @@ let y = &*std::ops::Deref::deref(&String::new()); // ERROR # y; ``` +r[expr.deref.diverging] +A dereference expression [diverges] if its operand diverges. + +```rust +fn dereference_diverges() -> ! { + // OK, expression diverges. + *{loop {}; &123}; +} +``` + +> [!NOTE] +> A dereference expression also diverges if the type of the dereferenced value is the [never type] and the value is guaranteed to be read. +> +> ```rust +> fn dereferenced_read(x: &!) -> ! { +> // OK, expression diverges. +> *x; +> } +> ``` +> +> ```rust,compile_fail,E0308 +> fn dereferenced_not_read(x: &!) -> ! { +> // This does not constitute a read, and thus does not diverge. +> let _ = *x; +> // ERROR: Expected type !, found () +> } +> ``` + r[expr.try] ## The try propagation expression @@ -325,6 +376,26 @@ The try propagation operator can be applied to expressions with the type of: - `Poll::Ready(None)` evaluates to `Poll::Ready(None)`. - `Poll::Pending` evaluates to `Poll::Pending`. +r[expr.try.diverging] +A try propagation expression [diverges] if its operand diverges. + +```rust +fn try_diverges() -> Option { + // OK, this diverges. + let _: ! = { loop {}; None }?; +} +``` + +> [!NOTE] +> A try propagation expression also diverges if the type of the unwrapped element is the [never type]. +> +> ```rust +> fn try_type_is_never(x: Option) -> Option { +> // OK, expression diverges. +> x?; +> } +> ``` + r[expr.negate] ## Negation operators @@ -357,6 +428,21 @@ assert_eq!(!x, -7); assert_eq!(true, !false); ``` +r[expr.negate.diverging] +A negation expression [diverges] if its operand diverges. + +```rust +fn neg_diverges() -> ! { + // OK, expression diverges. + -{ loop {}; 1 }; +} + +fn not_diverges() -> ! { + // OK, expression diverges. + !{ loop {}; true}; +} +``` + r[expr.arith-logic] ## Arithmetic and logical binary operators @@ -417,6 +503,21 @@ assert_eq!(13 << 3, 104); assert_eq!(-10 >> 2, -3); ``` +r[expr.arith-logic.diverging] +An arithmetic or logical expression [diverges] if either of its operands diverges. + +```rust +fn plus_lhs_diverges(x: !) -> ! { + // OK, expression diverges. + x as i32 + 1; +} + +fn plus_rhs_diverges(x: !) -> ! { + // OK, expression diverges. + 1 + x as i32; +} +``` + r[expr.cmp] ## Comparison operators @@ -475,6 +576,21 @@ assert!('A' <= 'B'); assert!("World" >= "Hello"); ``` +r[expr.cmp.diverging] +A comparison expression [diverges] if either of its operands diverges. + +```rust +fn cmp_lhs_diverges(x: !) -> ! { + // OK, expression diverges. + (x as i32) < 1; +} + +fn cmp_rhs_diverges(x: !) -> ! { + // OK, expression diverges. + 1 < (x as i32); +} +``` + r[expr.bool-logic] ## Lazy boolean operators @@ -496,6 +612,24 @@ let x = false || true; // true let y = false && panic!(); // false, doesn't evaluate `panic!()` ``` +r[expr.bool-logic.diverging] +A lazy boolean expression [diverges] only if its left-hand operand diverges. + +```rust +fn lazy_bool_lhs(x: !) -> ! { + // OK, expression diverges. + x || true; +} +``` + +```rust,compile_fail,E0308 +fn lazy_bool_rhs(x: !) -> ! { + // This expression does not diverge, thus the body does not diverge. + false || x; + // ERROR: Expected type !, found () +} +``` + r[expr.as] ## Type cast expressions @@ -547,6 +681,16 @@ r[expr.as.coercions] [^no-capture]: Only closures that do not capture (close over) any local variables can be cast to function pointers. +r[expr.as.diverging] +A type cast expression [diverges] if its expression operand diverges. + +```rust +fn cast_diverges(x: !) -> ! { + // OK, expression diverges. + x as i32; +} +``` + ### Semantics r[expr.as.numeric] @@ -920,6 +1064,23 @@ In its most basic form, an assignee expression is a [place expression], and we d r[expr.assign.behavior-destructuring] The more general case of destructuring assignment is discussed below, but this case always decomposes into sequential assignments to place expressions, which may be considered the more fundamental case. +r[expr.assign.diverging] +An assignment expression [diverges] if either of its operands diverges. + +```rust +fn assign_lhs_diverges() -> ! { + let a; + // OK, expression diverges. + *({loop {}; &a}) = 1; +} + +fn assign_rhs_diverges(x: !) -> ! { + let a: i32; + // OK, expression diverges. + a = x; +} +``` + r[expr.assign.basic] ### Basic assignments @@ -1229,10 +1390,28 @@ As with normal assignment expressions, compound assignment expressions always pr > [!WARNING] > Avoid writing code that depends on the evaluation order of operands in compound assignments as it can be unusual and surprising. +r[expr.compound-assign.diverging] +A compound assignment expression [diverges] if either of its operands diverges. + +```rust +fn compound_assign_lhs_diverges() -> ! { + let a: i32; + // OK, expression diverges. + *({loop {}; &a}) += 1; +} + +fn compound_assign_rhs_diverges(x: !) -> ! { + let a: i32; + // OK, expression diverges. + a += x as i32; +} +``` + [`Box`]: ../special-types-and-traits.md#boxt [`Try`]: core::ops::Try [autoref]: expr.method.candidate-receivers-refs [copies or moves]: ../expressions.md#moved-and-copied-types +[diverges]: divergence [dropping]: ../destructors.md [eval order test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs [explicit discriminants]: ../items/enumerations.md#explicit-discriminants @@ -1247,6 +1426,7 @@ As with normal assignment expressions, compound assignment expressions always pr [metadata]: dynamic-sized.pointer-types [moved from]: expr.move.movable-place [mutable]: ../expressions.md#mutability +[never type]: type.never [place expression]: ../expressions.md#place-expressions-and-value-expressions [assignee expression]: ../expressions.md#place-expressions-and-value-expressions [undefined behavior]: ../behavior-considered-undefined.md diff --git a/src/expressions/path-expr.md b/src/expressions/path-expr.md index fff9b9effa..cc515b7fb6 100644 --- a/src/expressions/path-expr.md +++ b/src/expressions/path-expr.md @@ -34,9 +34,32 @@ let slice_reverse = <[i32]>::reverse; r[expr.path.const] Evaluation of associated constants is handled the same way as [`const` blocks]. +r[expr.path.diverging] +A path expression [diverges] if the path resolves to a value with the [never type]. + +```rust +fn path_diverges(x: !) -> ! { + // OK, expression diverges. + x; +} +``` + +> [!NOTE] +> A path expression does not diverge if it is a place expression that is not guaranteed to be read. +> +> ```rust,compile_fail,E0308 +> fn path_not_read(x: !) -> ! { +> // The path is not read, thus the body does not diverge. +> let _ = x; +> // ERROR: Expected type !, found () +> } +> ``` + +[diverges]: divergence +[never type]: type.never +[path]: paths [place expressions]: ../expressions.md#place-expressions-and-value-expressions [value expressions]: ../expressions.md#place-expressions-and-value-expressions -[path]: ../paths.md [`static mut`]: ../items/static-items.md#mutable-statics [`unsafe` block]: block-expr.md#unsafe-blocks [`const` blocks]: block-expr.md#const-blocks diff --git a/src/expressions/range-expr.md b/src/expressions/range-expr.md index 16ec2414a5..3bc753af51 100644 --- a/src/expressions/range-expr.md +++ b/src/expressions/range-expr.md @@ -65,3 +65,20 @@ for i in 1..11 { println!("{}", i); } ``` + +r[expr.range.diverging] +A range expression [diverges] if any of its operands diverges. + +```rust +fn range_lhs_diverges(x: !) -> ! { + // OK, expression diverges. + (x..2); +} + +fn range_rhs_diverges(x: !) -> ! { + // OK, expression diverges. + (1..x); +} +``` + +[diverges]: divergence diff --git a/src/expressions/return-expr.md b/src/expressions/return-expr.md index 67569d5c5f..76af21d4bb 100644 --- a/src/expressions/return-expr.md +++ b/src/expressions/return-expr.md @@ -15,6 +15,18 @@ Evaluating a `return` expression moves its argument into the designated output l r[expr.return.diverging] A `return` expression is [diverging] and has a type of [`!`]. +```rust +fn return_diverges(x: !) -> ! { + // OK, expression diverges. + return x; +} + +fn return_no_value_diverges() { + // OK, expression diverges. + let _: ! = return; +} +``` + An example of a `return` expression: ```rust diff --git a/src/expressions/struct-expr.md b/src/expressions/struct-expr.md index 0046a7dbd5..7c54c5d07d 100644 --- a/src/expressions/struct-expr.md +++ b/src/expressions/struct-expr.md @@ -80,6 +80,25 @@ Enum::Variant {}; > let d = ColorSpace::Oklch {}; > ``` +r[expr.struct.diverging] +A struct expression [diverges] if any of its field operands diverges. + +```rust +struct S { + f1: i32, +} + +fn struct_field_diverges(x: !) -> ! { + // OK, expression diverges. + S { f1: x }; +} + +fn struct_base_diverges(x: !) -> ! { + // OK, expression diverges. + S { ..x }; +} +``` + r[expr.struct.field] ## Field struct expression @@ -139,6 +158,7 @@ Point3d { x: x, y: y_value, z: z }; Point3d { x, y: y_value, z }; ``` +[diverges]: divergence [enum variant]: ../items/enumerations.md [if let]: if-expr.md#if-let-patterns [if]: if-expr.md#if-expressions diff --git a/src/expressions/tuple-expr.md b/src/expressions/tuple-expr.md index f96dfa5cce..dd09a6edcd 100644 --- a/src/expressions/tuple-expr.md +++ b/src/expressions/tuple-expr.md @@ -40,6 +40,16 @@ Examples of tuple expressions and their types: | `("x".to_string(), )` | `(String, )` | | `("a", 4usize, true)`| `(&'static str, usize, bool)` | +r[expr.tuple.diverging] +A tuple expression [diverges] if any of its operands diverges. + +```rust +fn tuple_diverges(x: !) -> ! { + // OK, expression diverges. + (x,); +} +``` + r[expr.tuple-index] ## Tuple indexing expressions @@ -85,9 +95,20 @@ assert_eq!(point.1, 0.0); > [!NOTE] > Although arrays and slices also have elements, you must use an [array or slice indexing expression] or a [slice pattern] to access their elements. +r[expr.tuple-index.diverging] +A tuple indexing expression [diverges] if its expression operand diverges. + +```rust +fn tuple_index_diverges(x: (i32, i32)) -> ! { + // OK, expression diverges. + { loop {}; x }.1; +} +``` + [array or slice indexing expression]: array-expr.md#array-and-slice-indexing-expressions [call expression]: ./call-expr.md [decimal literal]: ../tokens.md#integer-literals +[diverges]: divergence [field access expressions]: ./field-expr.html#field-access-expressions [operands]: ../expressions.md [parenthetical expression]: grouped-expr.md diff --git a/src/expressions/underscore-expr.md b/src/expressions/underscore-expr.md index d717213280..14de46283d 100644 --- a/src/expressions/underscore-expr.md +++ b/src/expressions/underscore-expr.md @@ -37,3 +37,16 @@ _ = 2 + 2; // equivalent technique using a wildcard pattern in a let-binding let _ = 2 + 2; ``` + +r[expr.placeholder.diverging] +An underscore expression does not [diverge]. + +```rust,compile_fail,E0308 +fn underscore_does_not_diverge() -> ! { + // This expression does not diverge, thus the body does not diverge. + _ = 1; + // ERROR: Expected type !, found () +} +``` + +[diverge]: divergence diff --git a/src/inline-assembly.md b/src/inline-assembly.md index 8f7e535520..5a90358c21 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -1726,3 +1726,50 @@ On ARM, the following additional directives are guaranteed to be supported: - `.code` - `.thumb` - `.thumb_func` + +## Divergence + +r[asm.diverging.noreturn] +The `asm!` macro [diverges] if it uses the [`noreturn`] option and no `label` block returns unit. + +```rust +# #[cfg(target_arch = "x86_64")] { +fn noreturn_diverges() -> ! { + // OK, expression diverges. + unsafe { core::arch::asm!("ud2", options(noreturn)); } +} +# } +``` + +```rust,compile_fail,E0308 +fn noreturn_label_unit_does_not_diverge() -> ! { + // This expression does not diverge, thus the body does not diverge. + core::arch::asm!("jmp {}", label {}, options(noreturn)); + // ERROR: Expected type !, found () +} +``` + +```rust +fn noreturn_label_never_diverges() -> ! { + unsafe { + // OK, expression diverges. + core::arch::asm!("jmp {}", label { loop {}; }, options(noreturn)); + } +} +``` + +r[asm.diverging.naked_asm] +The `naked_asm!` macro always [diverges]. + +```rust +# #[cfg(target_arch = "x86_64")] { +#[unsafe(naked)] +extern "C" fn wrapper() -> ! { + // OK, expression diverges. + core::arch::naked_asm!("/* {} */", const 0); +} +# } +``` + +[`noreturn`]: asm.options.supported-options-noreturn +[diverges]: divergence diff --git a/src/statements.md b/src/statements.md index a3e89e8d81..86ac9a5499 100644 --- a/src/statements.md +++ b/src/statements.md @@ -89,6 +89,24 @@ let [u, v] = [v[0], v[1]] else { // This pattern is irrefutable, so the compiler }; ``` +r[statement.let.diverging] +A let statement [diverges] if its initializer diverges unless the initializer is a place that is not read. + +```rust +fn let_diverging(x: !) -> ! { + // OK: The let pattern is read causing this to diverge. + let a = x; +} +``` + +```rust,compile_fail,E0308 +fn let_diverging_not_read(x: !) -> ! { + // Initializer is not read, thus the body does not diverge. + let _ = x; + // ERROR: expected `!`, found `()` +} +``` + r[statement.expr] ## Expression statements @@ -142,6 +160,7 @@ r[statement.attribute] Statements accept [outer attributes]. The attributes that have meaning on a statement are [`cfg`], and [the lint check attributes]. [block]: expressions/block-expr.md +[diverges]: divergence [expression]: expressions.md [function]: items/functions.md [item]: items.md From 5929834f30561e67c4c04baed2706a3d50193682 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 15 Sep 2026 14:14:20 -0700 Subject: [PATCH 2/4] Add a description of what it means to "read" for the purpose of divergence A "read" when calculating divergence has a particular meaning that was previously was referred to but not defined. This adds a description of what that means. This is based on the implementation in [`expr_guaranteed_to_constitute_read_for_never`](https://github.com/rust-lang/rust/blame/0da8aedb7dc2e1b9fe7096050b7e215c2a4b451b/compiler/rustc_middle/src/hir/mod.rs#L217-L360) and [`is_guaranteed_to_constitute_read_for_never`](https://github.com/rust-lang/rust/blob/0da8aedb7dc2e1b9fe7096050b7e215c2a4b451b/compiler/rustc_hir/src/hir.rs#L1608-L1654). --- src/divergence.md | 92 ++++++++++++++++++++++++++++++++ src/expressions/array-expr.md | 2 +- src/expressions/block-expr.md | 2 +- src/expressions/field-expr.md | 2 +- src/expressions/if-expr.md | 2 +- src/expressions/match-expr.md | 2 +- src/expressions/operator-expr.md | 2 +- src/expressions/path-expr.md | 2 +- src/statements.md | 2 +- src/types/never.md | 17 +++++- 10 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index 3a57920b99..7bade2c922 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -90,6 +90,90 @@ Any expression of type [`!`] is a diverging expression. However, diverging expre > [!NOTE] > Divergence can propagate to the surrounding block. See [expr.block.diverging]. +r[divergence.place-read] +## Place expression reads for never types + +r[divergence.place-read.intro] +When a [place expression] is used in a context that calls for a value, the place is *read*. However, certain situations take the place itself without reading its value. In these situations, when the place's type is the [never type], then the expression is not considered to diverge. + +r[divergence.place-read.non-place] +A non-place expression always constitutes a read for divergence calculation. + +r[divergence.place-read.not-read] +A place expression constitutes a read for divergence calculation **except** for the following situations: + +- As the operand of a [borrow expression]. +- As the scrutinee of a [`match` expression], when not every arm's pattern constitutes a read (see [divergence.place-read.patterns]). +- As the initializer of a [`let` condition], when the pattern does not constitute a read. +- As the initializer of a [`let` statement], when the pattern does not constitute a read. + +> [!EXAMPLE] +> The following compile-fail examples are structured around [the behavior of block divergence][expr.block.diverging]. Each function returns `!`. Examples that contain a diverging place expression that is **not** read from cause the final type of the body to be `()`, which fails to type check. If the diverging place expression is read from, then every code path is diverging, and the type of the body would be `!` and it would compile successfully. +> +> ```rust,compile_fail,E0308 +> fn borrow_operand_not_read(x: !) -> ! { +> &x; +> // ERROR: expected `!`, found `()` +> } +> ``` +> +> ```rust,compile_fail,E0308 +> fn match_scrutinee_not_read_with_non_reading_patterns(x: !) -> ! { +> match x { +> _ => {} +> }; +> // ERROR: expected `!`, found `()` +> } +> ``` +> +> In contrast, when an arm uses a binding pattern (which constitutes a read), the scrutinee `x: !` is read, and reading an uninhabited place diverges. Note that the match is a statement here, just like the non-reading example above. The difference is that this statement is now considered diverging, so the block has the never type because the block diverges and [does not have a final expression][expr.block.value-diverges-no-trailing-expr]: +> +> ```rust +> fn match_scrutinee_read_with_reading_patterns(x: !) -> ! { +> match x { +> a => {} +> }; // OK: this statement diverges +> } +> ``` + +r[divergence.place-read.patterns] +A pattern *constitutes a read* of the value it is matched against in all cases except as follows: + +- The [wildcard pattern] `_` does not constitute a read. +- An [or-pattern] `p1 | p2 | ...` constitutes a read only if *all* of its alternatives constitute a read. + +```rust +fn pattern_is_read(x: !) -> ! { + // OK: Pattern is read, so the match expression diverges. + match x { + a => {} + }; +} +``` + +```rust,compile_fail,E0308 +fn wildcard_pattern_not_read(x: !) -> ! { + match x { + _ => {} + }; + // ERROR: expected `!`, found `()` +} +``` + +```rust +fn or_pattern_read_in_all_patterns(x: !) -> ! { + // OK: This statement is diverging. + let (a | a) = x; +} +``` + +```rust,compile_fail,E0308 +fn or_pattern_not_read_if_any_alternative_is_non_reading(x: !) -> ! { + let (_ | _) = x; + // ERROR: expected `!`, found `()` +} +``` + r[divergence.fallback] ## Fallback @@ -120,3 +204,11 @@ If a type to be inferred is only unified with diverging expressions, then that t [`!`]: type.never +[borrow expression]: expr.operator.borrow +[`let` condition]: expr.if.let +[`let` statement]: statement.let +[`match` expression]: expr.match +[never type]: type.never +[or-pattern]: patterns.or +[place expression]: expr.place-value.place-memory-location +[wildcard pattern]: patterns.wildcard.intro diff --git a/src/expressions/array-expr.md b/src/expressions/array-expr.md index 928bae3d2c..726cba1728 100644 --- a/src/expressions/array-expr.md +++ b/src/expressions/array-expr.md @@ -154,7 +154,7 @@ fn index_diverges(a: [i32; 1]) -> ! { ``` > [!NOTE] -> An index expression also diverges if the type of the indexed element is the [never type] and the value is guaranteed to be read (see [divergence.never]). +> An index expression also diverges if the type of the indexed element is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never]). > > ```rust > fn diverging_place_read(x: [!; 1]) -> ! { diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 9c54850e93..64c040974d 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -88,7 +88,7 @@ fn f() -> ! { loop {}; } // Diverges and has no final operand. > 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. +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][divergence.place-read]. ```rust,no_run fn no_control_flow() -> ! { diff --git a/src/expressions/field-expr.md b/src/expressions/field-expr.md index 12b0e62b95..4ffcf1e07d 100644 --- a/src/expressions/field-expr.md +++ b/src/expressions/field-expr.md @@ -57,7 +57,7 @@ fn field_diverge(x: !) -> ! { ``` > [!NOTE] -> A field expression also diverges if the type of the field is the [never type] and the value is guaranteed to be read (see [divergence.never]). +> A field expression also diverges if the type of the field is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never]). > > ```rust > struct S { diff --git a/src/expressions/if-expr.md b/src/expressions/if-expr.md index 6dd072bafd..6051363378 100644 --- a/src/expressions/if-expr.md +++ b/src/expressions/if-expr.md @@ -160,7 +160,7 @@ if let E::X(n) | E::Y(n) = v { ``` r[expr.if.let.diverging] -A `let` pattern causes the condition to diverge if the initializer diverges unless the initializer is a place that is not read. +A `let` pattern causes the condition to diverge if the initializer diverges unless the initializer is a place [that is not read][divergence.place-read.patterns]. ```rust fn if_let_diverging(x: !) -> ! { diff --git a/src/expressions/match-expr.md b/src/expressions/match-expr.md index e40682a7d0..02e20db0c8 100644 --- a/src/expressions/match-expr.md +++ b/src/expressions/match-expr.md @@ -151,7 +151,7 @@ fn match_some_arms_diverge(x: i32) -> ! { ``` r[expr.match.scrutinee.diverging] -A `match` expression diverges if the scrutinee diverges unless the scrutinee is a place expression and not every arm's pattern constitutes a read of that place. +A `match` expression diverges if the scrutinee diverges unless the scrutinee is a place expression and not every arm's pattern constitutes a read of that place (see [divergence.place-read.patterns]). ```rust fn match_scrutinee_diverges(x: !) -> ! { diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 287e15d24e..2d2b09ba2b 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -238,7 +238,7 @@ fn dereference_diverges() -> ! { ``` > [!NOTE] -> A dereference expression also diverges if the type of the dereferenced value is the [never type] and the value is guaranteed to be read. +> A dereference expression also diverges if the type of the dereferenced value is the [never type] and the value is [guaranteed to be read][divergence.place-read]. > > ```rust > fn dereferenced_read(x: &!) -> ! { diff --git a/src/expressions/path-expr.md b/src/expressions/path-expr.md index cc515b7fb6..d62b816ad6 100644 --- a/src/expressions/path-expr.md +++ b/src/expressions/path-expr.md @@ -45,7 +45,7 @@ fn path_diverges(x: !) -> ! { ``` > [!NOTE] -> A path expression does not diverge if it is a place expression that is not guaranteed to be read. +> A path expression does not diverge if it is a place expression that is not [guaranteed to be read][divergence.place-read]. > > ```rust,compile_fail,E0308 > fn path_not_read(x: !) -> ! { diff --git a/src/statements.md b/src/statements.md index 86ac9a5499..93bc9da768 100644 --- a/src/statements.md +++ b/src/statements.md @@ -90,7 +90,7 @@ let [u, v] = [v[0], v[1]] else { // This pattern is irrefutable, so the compiler ``` r[statement.let.diverging] -A let statement [diverges] if its initializer diverges unless the initializer is a place that is not read. +A let statement [diverges] if its initializer diverges unless the initializer is a place [that is not read][divergence.place-read.patterns]. ```rust fn let_diverging(x: !) -> ! { diff --git a/src/types/never.md b/src/types/never.md index 414862fe52..cb4556d161 100644 --- a/src/types/never.md +++ b/src/types/never.md @@ -50,7 +50,22 @@ NeverType -> `!` ``` r[type.never.coercion] -Expressions of type `!` can be coerced into any type. +An expression of type `!` can be coerced into any type unless it is a place [that is not read][divergence.place-read]. + +```rust +fn coerce_never_to_i32(x: !) { + // OK: The place is read, and x is coerced to i32. + let a: i32 = x; +} +``` + +```rust,compile_fail,E0308 +fn coerce_never_to_i32_not_read(x: !) { + // ERROR: expected i32, found `!` + // The place is not read, so never-to-any coercion does not apply. + let _: i32 = x; +} +``` > [!NOTE] > The standard library type [`Infallible`] is a type alias for `!`. From dfa2d13eedf396bf6462d66788b15b8bdd932e47 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 6 Oct 2026 15:04:21 +0000 Subject: [PATCH 3/4] Rework the never-type rule for unread places We have to explain why examples such as ```rust fn f() -> ! { let _: &! = &*&loop {}; } // OK. ``` and ```rust fn f(x: [!; 1]) -> ! { &x[{ loop {}; 0 }]; } // OK. ``` work. The rules, as written, would suggest that they should not. These work because, even if an unread place expression is of type `!`, if that place expression is diverging *anyway* due to having a diverging operand, then the place expression still diverges. So we need to narrow the rule that says that unread place expressions of type never do not diverge to say that unread place expressions of type never do not diverge *due only to their types*. Let's do that. --- src/divergence.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/divergence.md b/src/divergence.md index 7bade2c922..d327ccb812 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -94,7 +94,19 @@ r[divergence.place-read] ## Place expression reads for never types r[divergence.place-read.intro] -When a [place expression] is used in a context that calls for a value, the place is *read*. However, certain situations take the place itself without reading its value. In these situations, when the place's type is the [never type], then the expression is not considered to diverge. +When a [place expression] is used in a context that calls for a value, the place expression is *read*. But certain situations take the place itself without reading its value. In these situations, a place expression of the [never type] does not diverge only due to its type. + +```rust,compile_fail,E0308 +fn f(x: !) -> ! { + let _: &! = &x; +} // ERROR: Mismatched types. +``` + +```rust +fn f() -> ! { + let _: &! = &*&loop {}; +} // OK. +``` r[divergence.place-read.non-place] A non-place expression always constitutes a read for divergence calculation. From 282ce6ff953dbd9bcebce42e59b2e3c0b23d4064 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 6 Oct 2026 22:18:32 +0000 Subject: [PATCH 4/4] Rework the `divergence.never-*` rules We had said that any expression of type never is a diverging expression, but that's only true of value expressions. Let's nail this down more precisely, add examples, and split up the rule (which is better for adding examples). --- src/divergence.md | 41 +++++++++++++++++++++++++++-- src/expressions/array-expr.md | 2 +- src/expressions/block-expr.md | 2 +- src/expressions/call-expr.md | 2 +- src/expressions/closure-expr.md | 2 +- src/expressions/field-expr.md | 2 +- src/expressions/method-call-expr.md | 2 +- 7 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/divergence.md b/src/divergence.md index d327ccb812..62cbb1976e 100644 --- a/src/divergence.md +++ b/src/divergence.md @@ -62,8 +62,43 @@ See the following rules for specific expression divergence behavior: > [!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`). +r[divergence.never-value] +[Value expressions] of type [`!`] are diverging expressions. + +```rust +fn f() -> ! { let _: ! = loop {}; } // OK. +// ^^^^^^^ Diverging value expression. +``` + +r[divergence.never-place] +[Place expressions] of type [`!`] that are [read][divergence.place-read] are diverging expressions. + +```rust +fn f(x: !) -> ! { let _x = x; } +// ^ +// Reading this place expression of type `!` makes it diverge. +``` + +```rust,compile_fail,E0308 +fn f(x: !) -> ! { let _: ! = x; } // ERROR: Mismatched types. +// ^ +// This place expression is not read and does not diverge. +``` + +> [!NOTE] +> Other expressions, including expressions of other types, may also be diverging expressions when they contain diverging expressions. +> +> ```rust +> fn f1() -> ! { let _: Option = Some(loop {}); } // OK. +> // ^^^^^^^^^^^^^ +> // This value expression diverges despite not being of type `!`. +> fn f2() -> ! { let _: &! = &*&loop {}; } // OK. +> // ^^^^^^^^^ +> // This place expression is not read but diverges anyway. +> fn f3() -> ! { *{loop {}; &mut 0u8} = 0u8; } // OK. +> // ^^^^^^^^^^^^^^^^^^^^ +> // This assignee expression is diverging. +> ``` > [!NOTE] > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. @@ -223,4 +258,6 @@ If a type to be inferred is only unified with diverging expressions, then that t [never type]: type.never [or-pattern]: patterns.or [place expression]: expr.place-value.place-memory-location +[place expressions]: expr.place-value.place-memory-location +[value expressions]: expr.place-value.value-result [wildcard pattern]: patterns.wildcard.intro diff --git a/src/expressions/array-expr.md b/src/expressions/array-expr.md index 726cba1728..f4a459c168 100644 --- a/src/expressions/array-expr.md +++ b/src/expressions/array-expr.md @@ -154,7 +154,7 @@ fn index_diverges(a: [i32; 1]) -> ! { ``` > [!NOTE] -> An index expression also diverges if the type of the indexed element is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never]). +> An index expression also diverges if the type of the indexed element is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never-place]). > > ```rust > fn diverging_place_read(x: [!; 1]) -> ! { diff --git a/src/expressions/block-expr.md b/src/expressions/block-expr.md index 64c040974d..d456ee2c77 100644 --- a/src/expressions/block-expr.md +++ b/src/expressions/block-expr.md @@ -189,7 +189,7 @@ async fn async_block_does_not_diverge() -> ! { ``` > [!NOTE] -> Evaluating the future such that the output type is the [never type] will result in a value whose type is the never type, and per [divergence.never] the expression diverges. +> Evaluating the future such that the output type is the [never type] will result in a value whose type is the never type, and per [divergence.never-value] the expression diverges. > > ```rust > async fn async_block_await_diverge() -> ! { diff --git a/src/expressions/call-expr.md b/src/expressions/call-expr.md index a02c34e743..bb608ccfde 100644 --- a/src/expressions/call-expr.md +++ b/src/expressions/call-expr.md @@ -50,7 +50,7 @@ fn argument_diverges(x: !) -> ! { ``` > [!NOTE] -> If the return type of the called function is the [never type], then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> If the return type of the called function is the [never type], then the resulting value will have the never type, and per [divergence.never-value] the resulting expression diverges. > > ```rust > fn exit() -> ! { diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index 4ed1121f06..d39a14bfc2 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -42,7 +42,7 @@ fn closure_does_not_diverge() -> ! { ``` > [!NOTE] -> If the return type of the closure is the [never type], and the closure is called, then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> If the return type of the closure is the [never type], and the closure is called, then the resulting value will have the never type, and per [divergence.never-value] the resulting expression diverges. > > ```rust > fn closure_call_diverge() -> ! { diff --git a/src/expressions/field-expr.md b/src/expressions/field-expr.md index 4ffcf1e07d..6a94685f40 100644 --- a/src/expressions/field-expr.md +++ b/src/expressions/field-expr.md @@ -57,7 +57,7 @@ fn field_diverge(x: !) -> ! { ``` > [!NOTE] -> A field expression also diverges if the type of the field is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never]). +> A field expression also diverges if the type of the field is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never-place]). > > ```rust > struct S { diff --git a/src/expressions/method-call-expr.md b/src/expressions/method-call-expr.md index 2911d356fe..7e611be5c0 100644 --- a/src/expressions/method-call-expr.md +++ b/src/expressions/method-call-expr.md @@ -105,7 +105,7 @@ fn argument_diverges(x: !) -> ! { ``` > [!NOTE] -> If the return type of the called method is the [never type], then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges. +> If the return type of the called method is the [never type], then the resulting value will have the never type, and per [divergence.never-value] the resulting expression diverges. > > ```rust > struct S;