-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a section for the never type change in e2024
- Loading branch information
1 parent
070a55f
commit 8511445
Showing
1 changed file
with
141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# Never type fallback to itself | ||
|
||
🚧 The 2024 Edition has not yet been released and hence this section is still "under construction". | ||
|
||
## Summary | ||
|
||
- Never type (`!`) to any type coercions fallback to never type (`!`) | ||
|
||
## Details | ||
|
||
When the compiler sees a value of type ! in a [coercion site], | ||
it implicitly inserts a coercion to allow the type checker to infer any type: | ||
|
||
```rust | ||
// this | ||
let x: u8 = panic!(); | ||
|
||
// is (essentially) turned by the compiler into | ||
let x: u8 = absurd(panic!()); | ||
|
||
// where absurd is a function with the following signature | ||
// (it's sound, because `!` always marks unreachable code): | ||
fn absurd<T>(_: !) -> T { ... } | ||
``` | ||
|
||
This can lead to compilation errors if the type cannot be inferred: | ||
|
||
```rust | ||
// this | ||
{ panic!() }; | ||
|
||
// gets turned into this | ||
{ absurd(panic!()) }; // error: can't infer the type of `absurd` | ||
``` | ||
|
||
To prevent such errors, the compiler remembers where it inserted absurd calls, | ||
and if it can’t infer the type, it uses the fallback type instead: | ||
|
||
```rust | ||
type Fallback = /* An arbitrarily selected type! */; | ||
{ absurd::<Fallback>(panic!()) } | ||
``` | ||
|
||
This is what is known as “never type fallback”. | ||
|
||
Historically, the fallback type was `()`, causing confusing behavior where `!` spontaneously coerced to `()`, | ||
even when it would not infer `()` without the fallback. | ||
|
||
In the 2024 edition (and possibly in all editions on a later date) the fallback is now `!`. | ||
This makes it work more intuitively, now when you pass `!` and there is no reason to coerce it to | ||
something else, it is kept as `!`. | ||
|
||
In some cases your code might depend on the fallback being `()`, so this can cause compilation | ||
errors or changes in behavior. | ||
|
||
[coercion site]: https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites | ||
|
||
## Migration | ||
|
||
There is no automatic fix, but there is automatic detection of code which will be broken by the | ||
edition change. While still on a previous edition you should see warnings if your code will be | ||
broken. | ||
|
||
In either case the fix is to specify the type explicitly, so the fallback is not used. | ||
The complication is that it might not be trivial to see which type needs to be specified. | ||
|
||
One of the most common patterns which are broken by this change is using `f()?;` where `f` is | ||
generic over the ok-part of the return type: | ||
|
||
```rust | ||
fn f<T: Default>() -> Result<T, ()> { | ||
Ok(T::default()) | ||
} | ||
|
||
f()?; | ||
``` | ||
|
||
You might think that in this example type `T` can't be inferred, however due to the current | ||
desugaring of `?` operator it used to be inferred to `()`, but it will be inferred to `!` now. | ||
|
||
To fix the issue you need to specify the `T` type explicitly: | ||
|
||
```rust | ||
f::<()>()?; | ||
// OR | ||
() = f()?; | ||
``` | ||
|
||
Another relatively common case is `panic`king in a closure: | ||
|
||
```rust | ||
trait Unit {} | ||
impl Unit for () {} | ||
|
||
fn run(f: impl FnOnce() -> impl Unit) { | ||
f(); | ||
} | ||
|
||
run(|| panic!()); | ||
``` | ||
|
||
Previously `!` from the `panic!` coerced to `()` which implements `Unit`. | ||
However now the `!` is kept as `!` so this code fails because `!` does not implement `Unit`. | ||
To fix this you can specify return type of the closure: | ||
|
||
```rust | ||
run(|| -> () { panic!() }); | ||
``` | ||
|
||
A similar case to the `f()?` can be seen when using a `!`-typed expression in a branch and a | ||
function with unconstrained return in the other: | ||
|
||
```rust | ||
if true { | ||
Default::default() | ||
} else { | ||
return | ||
}; | ||
``` | ||
|
||
Previously `()` was inferred as the return type of `Default::default()` because `!` from `return` got spuriously coerced to `()`. | ||
Now, `!` will be inferred instead causing this code to not compile, because `!` does not implement `Default`. | ||
|
||
Again, this can be fixed by specifying the type explicitly: | ||
|
||
```rust | ||
() = if true { | ||
Default::default() | ||
} else { | ||
return | ||
}; | ||
|
||
// OR | ||
|
||
if true { | ||
<() as Default>::default() | ||
} else { | ||
return | ||
}; | ||
``` | ||
|