-
Notifications
You must be signed in to change notification settings - Fork 570
Add a chapter on divergence #2067
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4cfa363
2f39743
5eef4bc
8b6a6da
d56f184
90ef10f
d582def
eb381b7
e097e7e
ebc51fd
895c0dd
6475450
d4a758c
635678b
6590a44
876b727
a5923a2
7d95416
b48b527
c8fbfa1
ae7499e
d4de939
4572f45
0e40425
da671a8
87c2fc6
50def6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| r[divergence] | ||
| # Divergence | ||
|
|
||
| r[divergence.intro] | ||
| A *diverging expression* is an expression that never completes normal execution. | ||
|
|
||
| ```rust | ||
| fn diverges() -> ! { | ||
| panic!("This function never returns!"); | ||
| } | ||
|
|
||
| fn example() { | ||
| let x: i32 = diverges(); // This line never completes. | ||
| println!("This is never printed: {x}"); | ||
| } | ||
| ``` | ||
|
|
||
| See the following rules for specific expression divergence behavior: | ||
|
|
||
| - [type.never.constraint] --- Function calls returning `!`. | ||
| - [expr.loop.infinite.diverging] --- Infinite `loop` expressions. | ||
| - [expr.loop.break.diverging] --- `break` expressions. | ||
| - [expr.loop.continue.diverging] --- `continue` expressions. | ||
| - [expr.return.diverging] --- `return` expressions. | ||
| - [expr.if.diverging] --- `if` expressions. | ||
| - [expr.match.diverging] --- `match` expressions. | ||
| - [expr.match.empty] --- Empty `match` expressions. | ||
| - [expr.block.diverging] --- Block expressions. | ||
|
|
||
| > [!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<!>`). | ||
|
|
||
| > [!NOTE] | ||
| > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. | ||
| > | ||
| > ```rust,compile_fail,E0308 | ||
| > enum Empty {} | ||
| > fn make_never() -> ! {loop{}} | ||
| > fn make_empty() -> Empty {loop{}} | ||
| > | ||
| > fn diverging() -> ! { | ||
| > // This has a type of `!`. | ||
| > // So, the entire function is considered diverging. | ||
| > make_never(); | ||
| > // OK: The type of the body is `!` which matches the return type. | ||
| > } | ||
| > fn not_diverging() -> ! { | ||
| > // This type is uninhabited. | ||
| > // However, the entire function is not considered diverging. | ||
| > make_empty(); | ||
| > // ERROR: The type of the body is `()` but expected type `!`. | ||
| > } | ||
| > ``` | ||
|
|
||
| > [!NOTE] | ||
| > Divergence can propagate to the surrounding block. See [expr.block.diverging]. | ||
|
|
||
| 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. --> | ||
|
|
||
| [`!`]: type.never |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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. | ||||||||||||||||||
| 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][expr.block.diverging], in which case it is the [never type]. | ||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change isn't correct because of #2067 (comment) In that example, the final operand is loop {}, which has a type of !. But, the type of the entire block is usize. I think I maybe miscommunicated when I said "I like your wording" - because it seems nice, but has this flaw. My comment at the end is essentially the rule that needs to be here:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was what I was getting at when I said it could be a followup, since I don't fully understand all the bits here. The problem is that blocks with break expressions are treated separately from normal blocks in the reference, and we need to fit this all together. I tried with the following: diff --git a/src/expressions/loop-expr.md b/src/expressions/loop-expr.md
index 3d5b9420..bfe08687 100644
--- a/src/expressions/loop-expr.md
+++ b/src/expressions/loop-expr.md
@@ -311,6 +311,9 @@ Example:
r[expr.loop.break.value]
A `break` expression is only permitted in the body of a loop, and has one of the forms `break`, `break 'label` or ([see below](#break-and-loop-values)) `break EXPR` or `break 'label EXPR`.
+r[expr.loop.break-value.implicit-value]
+In the context of a [`loop` with break expressions][expr.loop.break-value] or a [labeled block expression], a `break` without an expression is considered identical to `break` with expression `()`.
+
r[expr.loop.block-labels]
## Labeled block expressions
@@ -347,6 +350,24 @@ 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
+> };
+> }
+> ```
+
+
r[expr.loop.continue]
## `continue` expressions
@@ -394,15 +415,33 @@ let result = loop {
assert_eq!(result, 13);
```
-r[expr.loop.break-value.loop]
-In the case a `loop` has an associated `break`, it is not considered diverging, and the `loop` must have a type compatible with each `break` expression.
-`break` without an expression is considered identical to `break` with expression `()`.
+r[expr.loop.break-value.type]
+A `loop` with an associated `break` does not [diverge], and its type is the [least upper bound] of all of the break operands.
+
+> [!EXAMPLE]
+> ```rust
+> fn example(condition: bool) {
+> let s = String::from("owned");
+>
+> let _: &str = loop {
+> if condition {
+> break &s; // &String coerced to &str via Deref
+> }
+> break "literal"; // &'static str coerced to &str
+> };
+> }
+> ```
[`!`]: type.never
[`if` condition chains]: if-expr.md#chains-of-conditions
[`if` expressions]: if-expr.md
[`match` expression]: match-expr.md
[boolean type]: ../types/boolean.md
+[diverge]: divergence
[diverging]: divergence
+[labeled block expression]: expr.loop.block-labels
+[least upper bound]: coerce.least-upper-bound
+[never type]: type.never
[scrutinee]: ../glossary.md#scrutinee
[temporary values]: ../expressions.md#temporaries
+[unit type]: type.tuple.unit
diff --git a/src/type-coercions.md b/src/type-coercions.md
index 91b773ee..6bef99c3 100644
--- a/src/type-coercions.md
+++ b/src/type-coercions.md
@@ -239,6 +239,8 @@ LUB coercion is used and only used in the following situations:
+ To find the common type for a series of if branches.
+ To find the common type for a series of match arms.
+ To find the common type for array elements.
++ To find the common type for a [labeled block expression] among the break operands and the final block operand.
++ To find the common type for an [`loop` expression with break expressions] among the break operands.
+ To find the type for the return type of a closure with multiple return statements.
+ To check the type for the return type of a function with multiple return statements.
@@ -325,5 +327,7 @@ precisely.
[type cast operator]: expressions/operator-expr.md#type-cast-expressions
[`Unsize`]: std::marker::Unsize
[`CoerceUnsized`]: std::ops::CoerceUnsized
+[labeled block expression]: expr.loop.block-labels
+[`loop` expression with break expressions]: expr.loop.break-value
[method-call expressions]: expressions/method-call-expr.md
[supertraits]: items/traits.md#supertraitsDoes that look correct? One rule I was particularly uncertain about was I'm also just guessing that the additions to the LUB list make sense.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That diff looks good to me for the most part.
fn example() {
let _: ! = loop {
break loop {};
};
}This is the particular logic: https://github.com/rust-lang/rust/blob/ba2a7d33741a7ade4dc78e5e335d60d358cd1749/compiler/rustc_hir_typeck/src/expr.rs#L826 (
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wording here can be something like
|
||||||||||||||||||
|
|
||||||||||||||||||
| ```rust | ||||||||||||||||||
| # fn fn_call() {} | ||||||||||||||||||
|
|
@@ -63,6 +63,64 @@ 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.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. | ||||||||||||||||||
|
|
||||||||||||||||||
| ```rust,no_run | ||||||||||||||||||
| # #![ feature(never_type) ] | ||||||||||||||||||
| 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 {} | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Note: This makes use of the unstable never type which is only available on | ||||||||||||||||||
| // Rust's nightly channel. This is done for illustration purposes. It is | ||||||||||||||||||
| // possible to encounter this scenario in stable Rust, but requires a more | ||||||||||||||||||
| // convoluted example. | ||||||||||||||||||
| struct Foo { | ||||||||||||||||||
| x: !, | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+98
to
+100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||
|
|
||||||||||||||||||
| fn make<T>() -> T { loop {} } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn diverging_place_read() -> ! { | ||||||||||||||||||
| let foo = Foo { x: make() }; | ||||||||||||||||||
| // A read of a place expression produces a diverging block. | ||||||||||||||||||
| let _x = foo.x; | ||||||||||||||||||
| } | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ```rust,compile_fail,E0308 | ||||||||||||||||||
| # #![ feature(never_type) ] | ||||||||||||||||||
| # fn make<T>() -> T { loop {} } | ||||||||||||||||||
| # struct Foo { | ||||||||||||||||||
| # x: !, | ||||||||||||||||||
| # } | ||||||||||||||||||
| fn diverging_place_not_read() -> ! { | ||||||||||||||||||
| let foo = Foo { x: make() }; | ||||||||||||||||||
| // Assignment to `_` means the place is not read. | ||||||||||||||||||
| let _ = foo.x; | ||||||||||||||||||
| } // ERROR: Mismatched types. | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| r[expr.block.value] | ||||||||||||||||||
| Blocks are always [value expressions] and evaluate the last operand in value expression context. | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -292,13 +350,16 @@ fn is_unix_platform() -> bool { | |||||||||||||||||
| [inner attributes]: ../attributes.md | ||||||||||||||||||
| [method]: ../items/associated-items.md#methods | ||||||||||||||||||
| [mutable reference]: ../types/pointer.md#mutables-references- | ||||||||||||||||||
| [never type]: type.never | ||||||||||||||||||
| [place expression]: expr.place-value.place-memory-location | ||||||||||||||||||
| [scopes]: ../names/scopes.md | ||||||||||||||||||
| [shared references]: ../types/pointer.md#shared-references- | ||||||||||||||||||
| [statement]: ../statements.md | ||||||||||||||||||
| [statements]: ../statements.md | ||||||||||||||||||
| [struct]: struct-expr.md | ||||||||||||||||||
| [the lint check attributes]: ../attributes/diagnostics.md#lint-check-attributes | ||||||||||||||||||
| [tuple expressions]: tuple-expr.md | ||||||||||||||||||
| [unit type]: type.tuple.unit | ||||||||||||||||||
| [unsafe operations]: ../unsafety.md | ||||||||||||||||||
| [value expressions]: ../expressions.md#place-expressions-and-value-expressions | ||||||||||||||||||
| [Loops and other breakable expressions]: expr.loop.block-labels | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.