Skip to content

Commit 67a5615

Browse files
committed
Add a section for the never type change in e2024
1 parent 713b114 commit 67a5615

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

src/rust-2024/never-type-fallback.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Never type fallback to itself
2+
3+
🚧 The 2024 Edition has not yet been released and hence this section is still "under construction".
4+
5+
## Summary
6+
7+
- Never type (`!`) to any type coercions fallback to never type (`!`)
8+
9+
## Details
10+
11+
When the compiler sees a value of type ! in a [coercion site],
12+
it implicitly inserts a coercion to allow the type checker to infer any type:
13+
14+
```rust
15+
// this
16+
let x: u8 = panic!();
17+
18+
// is (essentially) turned by the compiler into
19+
let x: u8 = absurd(panic!());
20+
21+
// where absurd is a function with the following signature
22+
// (it's sound, because `!` always marks unreachable code):
23+
fn absurd<T>(_: !) -> T { ... }
24+
```
25+
26+
This can lead to compilation errors if the type cannot be inferred:
27+
28+
```rust
29+
// this
30+
{ panic!() };
31+
32+
// gets turned into this
33+
{ absurd(panic!()) }; // error: can't infer the type of `absurd`
34+
```
35+
36+
To prevent such errors, the compiler remembers where it inserted absurd calls,
37+
and if it can’t infer the type, it uses the fallback type instead:
38+
39+
```rust
40+
type Fallback = /* An arbitrarily selected type! */;
41+
{ absurd::<Fallback>(panic!()) }
42+
```
43+
44+
This is what is known as “never type fallback”.
45+
46+
Historically, the fallback type was `()`, causing confusing behavior where `!` spontaneously coerced to `()`,
47+
even when it would not infer `()` without the fallback.
48+
49+
In the 2024 edition (and possibly in all editions on a later date) the fallback is now `!`.
50+
This makes it work more intuitively, now when you pass `!` and there is no reason to coerce it to
51+
something else, it is kept as `!`.
52+
53+
In some cases your code might depend on the fallback being `()`, so this can cause compilation
54+
errors or changes in behavior.
55+
56+
[coercion site]: https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites
57+
58+
## Migration
59+
60+
There is no automatic fix, but there is automatic detection of code which will be broken by the
61+
edition change. While still on a previous edition you should see warnings if your code will be
62+
broken.
63+
64+
In either case the fix is to specify the type explicitly, so the fallback is not used.
65+
The complication is that it might not be trivial to see which type needs to be specified.
66+
67+
One of the most common patterns which are broken by this change is using `f()?;` where `f` is
68+
generic over the ok-part of the return type:
69+
70+
```rust
71+
fn f<T: Default>() -> Result<T, ()> {
72+
Ok(T::default())
73+
}
74+
75+
f()?;
76+
```
77+
78+
You might think that in this example type `T` can't be inferred, however due to the current
79+
desugaring of `?` operator it used to be inferred to `()`, but it will be inferred to `!` now.
80+
81+
To fix the issue you need to specify the `T` type explicitly:
82+
83+
```rust
84+
f::<()>()?;
85+
// OR
86+
() = f()?;
87+
```
88+
89+
Another relatively common case is `panic`king in a closure:
90+
91+
```rust
92+
trait Unit {}
93+
impl Unit for () {}
94+
95+
fn run(f: impl FnOnce() -> impl Unit) {
96+
f();
97+
}
98+
99+
run(|| panic!());
100+
```
101+
102+
Previously `!` from the `panic!` coerced to `()` which implements `Unit`.
103+
However now the `!` is kept as `!` so this code fails because `!` does not implement `Unit`.
104+
To fix this you can specify return type of the closure:
105+
106+
```rust
107+
run(|| -> () { panic!() });
108+
```
109+
110+
A similar case to the `f()?` can be seen when using a `!`-typed expression in a branch and a
111+
function with unconstrained return in the other:
112+
113+
```rust
114+
if true {
115+
Default::default()
116+
} else {
117+
return
118+
};
119+
```
120+
121+
Previously `()` was inferred as the return type of `Default::default()` because `!` from `return` got spuriously coerced to `()`.
122+
Now, `!` will be inferred instead causing this code to not compile, because `!` does not implement `Default`.
123+
124+
Again, this can be fixed by specifying the type explicitly:
125+
126+
```rust
127+
() = if true {
128+
Default::default()
129+
} else {
130+
return
131+
};
132+
133+
// OR
134+
135+
if true {
136+
<() as Default>::default()
137+
} else {
138+
return
139+
};
140+
```
141+

0 commit comments

Comments
 (0)