From 67a561580ec51ed93f594fe3f5027ebc18368844 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Mon, 24 Jun 2024 20:54:39 +0200 Subject: [PATCH 1/8] Add a section for the never type change in e2024 --- src/rust-2024/never-type-fallback.md | 141 +++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/rust-2024/never-type-fallback.md diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md new file mode 100644 index 00000000..1957f039 --- /dev/null +++ b/src/rust-2024/never-type-fallback.md @@ -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 { ... } +``` + +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::(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() -> Result { + 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 +}; +``` + From 509ac7d0d6487f442bf5a0306ce5e156c95d31d6 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 28 Jun 2024 22:41:38 +0200 Subject: [PATCH 2/8] add `never-type-fallback.md` to `SUMMARY.md` --- src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 633df73b..86da9e55 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -51,3 +51,4 @@ - [Macro fragment specifiers](rust-2024/macro-fragment-specifiers.md) - [`unsafe extern` blocks](rust-2024/unsafe-extern.md) - [Unsafe attributes](rust-2024/unsafe-attributes.md) + - [never type fallback change](rust-2024/never-type-fallback.md) From 7d227ca46a9c86e65d7302136a5d09ba3d19b6df Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 28 Jun 2024 22:55:04 +0200 Subject: [PATCH 3/8] try to fixup code examples --- src/rust-2024/never-type-fallback.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index 1957f039..81d36f06 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -11,7 +11,7 @@ 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 +```rust,ignore (has placeholders) // this let x: u8 = panic!(); @@ -25,7 +25,7 @@ fn absurd(_: !) -> T { ... } This can lead to compilation errors if the type cannot be inferred: -```rust +```rust,ignore (uses code from previous example) // this { panic!() }; @@ -36,7 +36,7 @@ This can lead to compilation errors if the type cannot be inferred: 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 +```rust,ignore (has placeholders, uses code from previous example) type Fallback = /* An arbitrarily selected type! */; { absurd::(panic!()) } ``` @@ -67,7 +67,7 @@ The complication is that it might not be trivial to see which type needs to be s 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 +```rust,ignore (can't compile outside of a result-returning function) fn f() -> Result { Ok(T::default()) } @@ -80,7 +80,7 @@ desugaring of `?` operator it used to be inferred to `()`, but it will be inferr To fix the issue you need to specify the `T` type explicitly: -```rust +```rust,ignore (can't compile outside of a result-returning function, mentions function from previous example) f::<()>()?; // OR () = f()?; @@ -88,11 +88,11 @@ f::<()>()?; Another relatively common case is `panic`king in a closure: -```rust +```rust,edition2015,should_panic trait Unit {} impl Unit for () {} -fn run(f: impl FnOnce() -> impl Unit) { +fn run(f: impl FnOnce() -> R) { f(); } @@ -103,14 +103,14 @@ 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 +```rust,ignore (uses function from the previous example) 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 +```rust,edition2015 if true { Default::default() } else { From f51cd8079f1b3e14e48d5cc4f53732bceac89512 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 29 Jun 2024 23:25:02 +0000 Subject: [PATCH 4/8] Capitalize and alphabetize summary entry --- src/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 86da9e55..27ae94d9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -49,6 +49,6 @@ - [Rustfmt: Combine all delimited exprs as last argument](rust-2024/rustfmt-overflow-delimited-expr.md) - [`gen` keyword](rust-2024/gen-keyword.md) - [Macro fragment specifiers](rust-2024/macro-fragment-specifiers.md) + - [Never type fallback change](rust-2024/never-type-fallback.md) - [`unsafe extern` blocks](rust-2024/unsafe-extern.md) - [Unsafe attributes](rust-2024/unsafe-attributes.md) - - [never type fallback change](rust-2024/never-type-fallback.md) From 6a2e762fd2901e426fd491b6885d6d1dedab80c0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 29 Jun 2024 23:27:16 +0000 Subject: [PATCH 5/8] Align page title with summary title Aside from aligning it with the summary entry, another reason to say "never type fallback change" is that if we were to say "to itself", we should say "never type fall back to itself", grammatically. But it seems better here to just use the noun form, so we'll be consistent with that. --- src/rust-2024/never-type-fallback.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index 81d36f06..3fe91bc9 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -1,4 +1,4 @@ -# Never type fallback to itself +# Never type fallback change 🚧 The 2024 Edition has not yet been released and hence this section is still "under construction". From 8567b1596e83a664ddfdbfdff59aac77668bd04f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 29 Jun 2024 23:30:31 +0000 Subject: [PATCH 6/8] Clean up trailing whitespace --- src/rust-2024/never-type-fallback.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index 3fe91bc9..d95f04f6 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -50,7 +50,7 @@ In the 2024 edition (and possibly in all editions on a later date) the fallback 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 +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 @@ -138,4 +138,3 @@ if true { return }; ``` - From 2eea6dcdc69e4183296f42e67d868e1d70d69b54 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 29 Jun 2024 23:33:27 +0000 Subject: [PATCH 7/8] Unwrap lines The line wrapping was a bit inconsistent. Let's just unwrap all the lines on this page. --- src/rust-2024/never-type-fallback.md | 41 +++++++++------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index d95f04f6..f593df0c 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -4,12 +4,11 @@ ## Summary -- Never type (`!`) to any type coercions fallback to never type (`!`) +- 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: +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,ignore (has placeholders) // this @@ -33,8 +32,7 @@ This can lead to compilation errors if the type cannot be inferred: { 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: +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,ignore (has placeholders, uses code from previous example) type Fallback = /* An arbitrarily selected type! */; @@ -43,29 +41,21 @@ type Fallback = /* An arbitrarily selected type! */; 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. +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 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. +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. +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. +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: +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,ignore (can't compile outside of a result-returning function) fn f() -> Result { @@ -75,8 +65,7 @@ fn f() -> Result { 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. +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: @@ -99,16 +88,13 @@ fn run(f: impl FnOnce() -> R) { 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: +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,ignore (uses function from the previous example) 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: +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,edition2015 if true { @@ -118,8 +104,7 @@ if true { }; ``` -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`. +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: From 42b054dc3161156831e2182bfd72e39d5f687c15 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 30 Jun 2024 19:18:53 +0000 Subject: [PATCH 8/8] 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. --- src/rust-2024/never-type-fallback.md | 101 +++++++++++++++++---------- 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index f593df0c..36f0648a 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -4,80 +4,98 @@ ## Summary -- Never type (`!`) to any type coercions fallback to never type (`!`). +- Never type (`!`) to any type ("never-to-any") coercions fall back to never type (`!`) rather than to unit 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: +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: -```rust,ignore (has placeholders) -// this +```rust,should_panic +# #![feature(never_type)] +// This: let x: u8 = panic!(); -// is (essentially) turned by the compiler into +// ...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 { ... } +// ...where `absurd` is the following function +// (it's sound because `!` always marks unreachable code): +fn absurd(x: !) -> T { x } ``` This can lead to compilation errors if the type cannot be inferred: -```rust,ignore (uses code from previous example) -// this +```rust,compile_fail,E0282 +# #![feature(never_type)] +# fn absurd(x: !) -> T { x } +// This: { panic!() }; -// gets turned into this -{ absurd(panic!()) }; // error: can't infer the type of `absurd` +// ...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: +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,ignore (has placeholders, uses code from previous example) -type Fallback = /* An arbitrarily selected type! */; +```rust,should_panic +# #![feature(never_type)] +# fn absurd(x: !) -> T { x } +type Fallback = /* An arbitrarily selected type! */ !; { absurd::(panic!()) } ``` -This is what is known as “never type fallback”. +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. +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. -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 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 `!`. -In some cases your code might depend on the fallback being `()`, so this can cause compilation errors or changes in behavior. +In some cases your code might depend on the fallback type being `()`, so this can cause compilation errors or changes in behavior. -[coercion site]: https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites +[coercion site]: ../../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. +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. -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. +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. -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: +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: -```rust,ignore (can't compile outside of a result-returning function) +```rust +# #![allow(dependency_on_unit_never_type_fallback)] +# fn outer(x: T) -> Result { fn f() -> Result { Ok(T::default()) } f()?; +# Ok(x) +# } ``` -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. +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 `!`. To fix the issue you need to specify the `T` type explicitly: -```rust,ignore (can't compile outside of a result-returning function, mentions function from previous example) + +```rust +# #![deny(dependency_on_unit_never_type_fallback)] +# fn outer(x: T) -> Result { +# fn f() -> Result { +# Ok(T::default()) +# } f::<()>()?; -// OR +// ...or: () = f()?; +# Ok(x) +# } ``` -Another relatively common case is `panic`king in a closure: +Another relatively common case is panicking in a closure: -```rust,edition2015,should_panic +```rust,should_panic +# #![allow(dependency_on_unit_never_type_fallback)] trait Unit {} impl Unit for () {} @@ -88,15 +106,24 @@ fn run(f: impl FnOnce() -> R) { 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,ignore (uses function from the previous example) +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: + + +```rust,should_panic +# #![deny(dependency_on_unit_never_type_fallback)] +# trait Unit {} +# impl Unit for () {} +# +# fn run(f: impl FnOnce() -> R) { +# f(); +# } 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: +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: -```rust,edition2015 +```rust +# #![allow(dependency_on_unit_never_type_fallback)] if true { Default::default() } else { @@ -104,18 +131,20 @@ if true { }; ``` -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`. +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`. Again, this can be fixed by specifying the type explicitly: + ```rust +# #![deny(dependency_on_unit_never_type_fallback)] () = if true { Default::default() } else { return }; -// OR +// ...or: if true { <() as Default>::default()