Skip to content

Commit 42b054d

Browse files
committed
Make an editing pass and make all tests work
On the page for the never type fallback change, we've made a general copyediting pass. As part of this, we've also fixed all of the tests such that they are checked and not simply ignored. Strangely, some of the longer explanations that had been added to the ignored tests were causing weird formatting issues in the rendered output. The example blocks weren't taking up the full width, and text was flowing around them. Making the tests work instead, and removing these explanations, resolves this issue.
1 parent 2eea6dc commit 42b054d

File tree

1 file changed

+65
-36
lines changed

1 file changed

+65
-36
lines changed

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

+65-36
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,98 @@
44

55
## Summary
66

7-
- Never type (`!`) to any type coercions fallback to never type (`!`).
7+
- Never type (`!`) to any type ("never-to-any") coercions fall back to never type (`!`) rather than to unit type (`()`).
88

99
## Details
1010

11-
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:
11+
When the compiler sees a value of type `!` (never) in a [coercion site][], it implicitly inserts a coercion to allow the type checker to infer any type:
1212

13-
```rust,ignore (has placeholders)
14-
// this
13+
```rust,should_panic
14+
# #![feature(never_type)]
15+
// This:
1516
let x: u8 = panic!();
1617
17-
// is (essentially) turned by the compiler into
18+
// ...is (essentially) turned by the compiler into:
1819
let x: u8 = absurd(panic!());
1920
20-
// where absurd is a function with the following signature
21-
// (it's sound, because `!` always marks unreachable code):
22-
fn absurd<T>(_: !) -> T { ... }
21+
// ...where `absurd` is the following function
22+
// (it's sound because `!` always marks unreachable code):
23+
fn absurd<T>(x: !) -> T { x }
2324
```
2425

2526
This can lead to compilation errors if the type cannot be inferred:
2627

27-
```rust,ignore (uses code from previous example)
28-
// this
28+
```rust,compile_fail,E0282
29+
# #![feature(never_type)]
30+
# fn absurd<T>(x: !) -> T { x }
31+
// This:
2932
{ panic!() };
3033
31-
// gets turned into this
32-
{ absurd(panic!()) }; // error: can't infer the type of `absurd`
34+
// ...gets turned into this:
35+
{ absurd(panic!()) }; //~ ERROR can't infer the type of `absurd`
3336
```
3437

35-
To prevent such errors, the compiler remembers where it inserted absurd calls, and if it cant infer the type, it uses the fallback type instead:
38+
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:
3639

37-
```rust,ignore (has placeholders, uses code from previous example)
38-
type Fallback = /* An arbitrarily selected type! */;
40+
```rust,should_panic
41+
# #![feature(never_type)]
42+
# fn absurd<T>(x: !) -> T { x }
43+
type Fallback = /* An arbitrarily selected type! */ !;
3944
{ absurd::<Fallback>(panic!()) }
4045
```
4146

42-
This is what is known as never type fallback.
47+
This is what is known as "never type fallback".
4348

44-
Historically, the fallback type was `()`, causing confusing behavior where `!` spontaneously coerced to `()`, even when it would not infer `()` without the fallback.
49+
Historically, the fallback type has been `()` (unit). This caused `!` to spontaneously coerce to `()` even when the compiler would not infer `()` without the fallback. That was confusing and has prevented the stabilization of the `!` type.
4550

46-
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 `!`.
51+
In the 2024 edition, the fallback type is now `!`. (We plan to make this change across all editions at a later date.) This makes things work more intuitively. Now when you pass `!` and there is no reason to coerce it to something else, it is kept as `!`.
4752

48-
In some cases your code might depend on the fallback being `()`, so this can cause compilation errors or changes in behavior.
53+
In some cases your code might depend on the fallback type being `()`, so this can cause compilation errors or changes in behavior.
4954

50-
[coercion site]: https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites
55+
[coercion site]: ../../reference/type-coercions.html#coercion-sites
5156

5257
## Migration
5358

54-
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.
59+
There is no automatic fix, but there is automatic detection of code that will be broken by the edition change. While still on a previous edition you will see warnings if your code will be broken.
5560

56-
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.
61+
The fix is to specify the type explicitly so that the fallback type is not used. Unfortunately, it might not be trivial to see which type needs to be specified.
5762

58-
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:
63+
One of the most common patterns broken by this change is using `f()?;` where `f` is generic over the `Ok`-part of the return type:
5964

60-
```rust,ignore (can't compile outside of a result-returning function)
65+
```rust
66+
# #![allow(dependency_on_unit_never_type_fallback)]
67+
# fn outer<T>(x: T) -> Result<T, ()> {
6168
fn f<T: Default>() -> Result<T, ()> {
6269
Ok(T::default())
6370
}
6471

6572
f()?;
73+
# Ok(x)
74+
# }
6675
```
6776

68-
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.
77+
You might think that, in this example, type `T` can't be inferred. However, due to the current desugaring of the `?` operator, it was inferred as `()`, and it will now be inferred as `!`.
6978

7079
To fix the issue you need to specify the `T` type explicitly:
7180

72-
```rust,ignore (can't compile outside of a result-returning function, mentions function from previous example)
81+
<!-- TODO: edition2024 -->
82+
```rust
83+
# #![deny(dependency_on_unit_never_type_fallback)]
84+
# fn outer<T>(x: T) -> Result<T, ()> {
85+
# fn f<T: Default>() -> Result<T, ()> {
86+
# Ok(T::default())
87+
# }
7388
f::<()>()?;
74-
// OR
89+
// ...or:
7590
() = f()?;
91+
# Ok(x)
92+
# }
7693
```
7794

78-
Another relatively common case is `panic`king in a closure:
95+
Another relatively common case is panicking in a closure:
7996

80-
```rust,edition2015,should_panic
97+
```rust,should_panic
98+
# #![allow(dependency_on_unit_never_type_fallback)]
8199
trait Unit {}
82100
impl Unit for () {}
83101
@@ -88,34 +106,45 @@ fn run<R: Unit>(f: impl FnOnce() -> R) {
88106
run(|| panic!());
89107
```
90108

91-
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:
92-
93-
```rust,ignore (uses function from the previous example)
109+
Previously `!` from the `panic!` coerced to `()` which implements `Unit`. However now the `!` is kept as `!` so this code fails because `!` doesn't implement `Unit`. To fix this you can specify the return type of the closure:
110+
111+
<!-- TODO: edition2024 -->
112+
```rust,should_panic
113+
# #![deny(dependency_on_unit_never_type_fallback)]
114+
# trait Unit {}
115+
# impl Unit for () {}
116+
#
117+
# fn run<R: Unit>(f: impl FnOnce() -> R) {
118+
# f();
119+
# }
94120
run(|| -> () { panic!() });
95121
```
96122

97-
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:
123+
A similar case to that of `f()?` can be seen when using a `!`-typed expression in one branch and a function with an unconstrained return type in the other:
98124

99-
```rust,edition2015
125+
```rust
126+
# #![allow(dependency_on_unit_never_type_fallback)]
100127
if true {
101128
Default::default()
102129
} else {
103130
return
104131
};
105132
```
106133

107-
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`.
134+
Previously `()` was inferred as the return type of `Default::default()` because `!` from `return` was spuriously coerced to `()`. Now, `!` will be inferred instead causing this code to not compile because `!` does not implement `Default`.
108135

109136
Again, this can be fixed by specifying the type explicitly:
110137

138+
<!-- TODO: edition2024 -->
111139
```rust
140+
# #![deny(dependency_on_unit_never_type_fallback)]
112141
() = if true {
113142
Default::default()
114143
} else {
115144
return
116145
};
117146

118-
// OR
147+
// ...or:
119148

120149
if true {
121150
<() as Default>::default()

0 commit comments

Comments
 (0)