-
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: |
||||||||||||||||||
|
|
||||||||||||||||||
| ```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.