-
Notifications
You must be signed in to change notification settings - Fork 564
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
84a8e30
2403b18
a9d8264
966e4b3
31f840e
375350a
f9cf9f5
09d0f2b
4f34b11
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,56 @@ | ||
| r[divergence] | ||
| # Divergence | ||
|
|
||
| r[divergence.intro] | ||
| If an expression diverges, then nothing after that expression will execute. Importantly, while there are certain language constructs that immediately produce a _diverging expression_ of the type [`!`](./types/never.md), divergence can also propogate to the surrounding block --- where divergence indicates that the block itself will never finish executing. | ||
|
|
||
| Any expression of type [`!`](./types/never.md) is a _diverging expression_, but there are also diverging expressions which are not of type `!` (e.g. `Some(loop {})` produces a type of `Option<!>`). | ||
|
|
||
| > [!NOTE] | ||
| > Though `!` is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge. | ||
| > | ||
| > ```rust,compile_fail,E0308 | ||
| > # #![ feature(never_type) ] | ||
| > # fn make<T>() -> T { loop {} } | ||
| > enum Empty {} | ||
| > fn diverging() -> ! { | ||
| > // This has a type of `!`. | ||
| > // So, the entire function is considered diverging | ||
| > make::<!>(); | ||
| > } | ||
| > fn not_diverging() -> ! { | ||
| > // This type is uninhabited. | ||
| > // However, the entire function is not considered diverging | ||
| > make::<Empty>(); | ||
| > } | ||
| > ``` | ||
|
|
||
| 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. --> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ r[items.fn.signature] | |
| Functions may declare a set of *input* [*variables*][variables] as parameters, through which the caller passes arguments into the function, and the *output* [*type*][type] of the value the function will return to its caller on completion. | ||
|
|
||
| r[items.fn.implicit-return] | ||
| If the output type is not explicitly stated, it is the [unit type]. | ||
| If the output type is not explicitly stated, it is the [unit type]. However, if the block expression is not [diverging](../divergence.md), then the output type is instead [`!`](../types/never.md). | ||
|
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. In talking over this PR in the office hours, @ehuss and I puzzled over this rule. Presumably the not is misplaced, but even with that removed, we weren't clear on how to assign meaning to this given, of course: fn f() {
loop {} // Diverging expression.
}
let _ = || -> ! { f() }; //~ ERROR mismatched types
let _: fn() -> ! = f; //~ ERROR mismatched types
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. Ah, this is a good catch. Indeed, the additional language is not correct for the function signature. What I had in mind when writing this was that when type checking the function body the implicit return is I think it was motivated by this example. I guess, maybe there is an equivalent statement missing about the implicit "final type" of a block (which is by default |
||
|
|
||
| r[items.fn.fn-item-type] | ||
| When referred to, a _function_ yields a first-class *value* of the corresponding zero-sized [*function item type*], which when called evaluates to a direct call to the function. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.