---
title: "Design meeting 2024-03-13: The never type"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2024-03-13
discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202024-03-13
url: https://hackmd.io/CwxuSa2jT7Wr_Ma86Mt4dg
---
# Exposition
[exposition]: #exposition
There is a desire to have a canonical uninhabited type (a type with no values). Since 2016[^1] Rust has such type: `!` (also known as "never").
However, this type is only fully available on nightly Rust (via `#![feature(never_type)]`) or via hacks (see [Appendix E]). There were numerous attempts to stabilize the never type, however they all failed, mainly because every time it turned out to be a significant breaking change.
I've read through the [tracking issue](https://github.com/rust-lang/rust/issues/35121) and various discussions linked there and tried collecting the most important things here. This document describes the problems that stand between us and the stabilization of `!`, and proposes a way forward.
Note though that I'm but a human and probably lost some context in the hurry of things. Still, I believe this document captures the most important events in the hisroty of the never type and has enough context to discuss the plan for the future.
# Goals
[goals]: #goals
This is what we wish to achieve alongside the stabilization:
1. Spontaneous decay rules are easy to teach and remember
2. Limit spontaneous never type decay as much as possible (fallback to `!` most of the time) (spontaneous decay is weird and unintuitive, more on this in [Spontaneous decay])
3. No existing code is broken (we still have our backwards compatibility guarantees!)
- Avoid unsoundness or at least lint in existing code, not easy to shoot yourself in the foot
4. Make `!` unify with `Infallible`
5. Common situations don't get unbound inference variables
(list adopted from a [recent T-lang meeting](https://hackmd.io/pKJu7TqtQHqOfaOXtZBw8w?view#The-Never-type) with slight modifications)
Those are roughly ordered in "most important to least important" (in the opinion of the document's author). I think it's important to have a simple and easy to understand design, never type as a concept is already slightly confusing for users, ideally we wouldn't add unnecessary complexity (points 1 and 2). Breaking code is obviously undesirable (point 3). `Infallible` was always supposed to be `!`, but in my opinion it's not the most important thing (point 4). Lastly, point 5 is just a usability concern, unnecessary errors are not nice (it is satisfied by all ideas in this document though, so is the least important).
# Characters
[characters]: #characters
### `!`, the never type
[`!`, the never type]: #-the-never-type
The never type is the canonical uninhabited type. It can be used to disable enum variants (`Result<T, !>` can only be `Ok`) and in general to represents the type of computations which never resolve to any value at all (like an infinite loop or a panic).
On stable Rust it can be used only as a function return type (although that allows to get it as a type via hacks, see [Appendix E]):
```rust
fn abort() -> ! { ... } // stable
```
A lot of things that concern control flow have `!` type. For example: `return`, `break`, `continue`, `panic!()`, `loop{}`[^3].
### never-to-any coercion
[never-to-any coercion]: #never-to-any-coercion
`!` can be implicitly coerced to any other type. This is sound because `!` is uninhabited — we know the code which has a value of `!` is unreachable.
This is an important coercion used everywhere on stable Rust:
```rust
match res {
Ok(x) => x as u32,
Err(_) => panic!() /* never-to-any */,
}
```
`panic!()` has type `!`, but since all arms of a match must have the same type, it is coerced to `u32`. This is extremely handy.
### `std::convert::Infallible`
[`std::convert::Infallible`]: #stdconvertInfallible
This is an uninhabited enum which is used to make a `Result` which can only be `Ok` in `std`. This is useful when a trait demands a `Result<_, _>` but your specific implementation is infallible.
Since its implementation there always was a desired to change `Infallible` to an alias to `!`[^4]. However, this caused problems in the past.
### Edition 2024
[edition 2024]: #edition-2024
An upcoming edition where we could introduce a breaking change[^5]. Will this allow us to finally do something about the never type?
### Never type spontaneous decay (aka never type fallback)
[Spontaneous decay]: #Never-type-spontaneous-decay-aka-never-type-fallback
The main culprit and the reason why so many attempts to stabilize `!` failed.
Because of backward compatibility issues, introduction of `!` had to introduce a hack -- spontaneous never type decay.
This effect is more commonly known as the never type fallback. However, in this document I refer to it as "spontaneous never type decay", because I think it makes it easier to explain. (it also removes the ambiguety of "what the fallback is")
Spontaneous decay makes `!` coerce to `()`, even when this is not required. For example ([play](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3e33393a1125c144396d0204f7dfd6bf)):
```rust
fn print_ret_ty<T>(_: impl FnOnce() -> T) {
dbg!(std::any::type_name::<T>());
}
print_ret_ty(|| loop {});
```
One would expect it to print `"!"` -- `loop {}` is `!`[^3], so the closure should return `!` too. However reality is different:
```
[src/main.rs:2:5] std::any::type_name::<T>() = "()"
```
As you can see the never "returned" by `loop {}` spontaneously decayed to `()`, making the closure return `()` and the function print `"()"`. This is quite confusing, as nothing in the program required this coercion. To my knowledge never-to-any is currently the only coercion that can happen spontaneously.
The "fallback" termionology commonly used for this refers to the fact that compiler sees this program more like this:
```rust
fn never_to_any<T>(never: !) -> T { ... }
fn print_ret_ty<T>(_: impl FnOnce() -> T) { ... }
print_ret_ty(|| never_to_any(loop {}));
```
With explicitly added coercion, you can notice that this won't type check. There is nothing allowing the compiler to infer `T` -- it is unbounded. The current compiler has a fallback to `()` -- if the "return type" of a never-to-any coercion cannot be inferred, it is set to `()`. Ideally we would set it to `!`, making `!`'s behavior more intuitive (i.e. passing it to a generic function keeps the type, as with any other type). However this breaks some code that depends on the fallback being `()`.
# Previous attempts
[previous attempts]: #previous-attempts
This section contains a best-effort recollection of the past attempts to stabilize `!`, with links to related PRs/issues/comments and reasons why they did not succeed.
### Attempt 1 (2018-01)
[attempt 1]: #attempt-1
- Stabilization PR: https://github.com/rust-lang/rust/pull/47630
- `Infallible = !` PR: https://github.com/rust-lang/rust/pull/49038
- `other_uninhabited=!` PR: https://github.com/rust-lang/rust/pull/49039 (that discovered the issue)
- Stabilization summary: https://github.com/rust-lang/rust/issues/48950
- FCP: https://github.com/rust-lang/rust/issues/35121#issuecomment-366372239
- Revert issue: https://github.com/rust-lang/rust/issues/49691
- Revert PR: https://github.com/rust-lang/rust/pull/50121
- Issue with the stabilization: https://github.com/rust-lang/rust/issues/49593
So the reason the first attempt was reverted was this example (sometimes generated by a macro):
```rust
pub fn a(x: Infallible) -> Box<dyn Error> {
Box::new(x)
}
```
That is fine (`Infallible: Error`), but when making `Infallible = !` this example used to not compile because compiler decided to coerce `x` to `dyn Error` causing errors because `Box::new` only accepts sized types.
Currently it fails for a different reason ([play](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=93f748b82ff6fba511db9104466df2f0)):
```rust
#![feature(never_type)]
pub fn b(x: !) -> Box<dyn Error> {
Box::new(x)
//~^ error[E0277]: the trait bound `(): Error` is not satisfied
}
```
This fails because `x` spontaneously decays to `()`, which does not implement error. The error is extremely confusing, if you don't know about the spontaneous decay.
Disabling spontaneous decay fixes the issue once more ([play](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=8df715b63118d8b40919e347fa4b667d)) (see [Appendix C] for notes about `never_type_fallback` feature):
```rust
#![feature(never_type, never_type_fallback)]
pub fn c(x: !) -> Box<dyn Error> {
Box::new(x)
}
```
### Attempt 2 (2018-12)
[attempt 2]: #attempt-2
- Issue/proposal to stabilize again: https://github.com/rust-lang/rust/issues/57012
- Stabilization report: https://github.com/rust-lang/rust/issues/57012#issuecomment-452398538
- FCP cancel: https://github.com/rust-lang/rust/issues/57012#issuecomment-541381519
This attempt proposed to stabilize `!` & disable the spontaneous decay. However, it was eventually closed in favour of the next one.
### Attempt 3 (2019-10)
[attempt 3]: #attempt-3
- Stabilization PR: https://github.com/rust-lang/rust/pull/65355
- Issue with the stabilization: https://github.com/rust-lang/rust/issues/66757
- Revert PR: https://github.com/rust-lang/rust/pull/67224
Originally this attempt was identical to the previous one, but [later](https://github.com/rust-lang/rust/pull/65355#issuecomment-546910731) tried to only stabilize `!` without the decay/fallback change.
The (minimized) issue looks like this:
```rust
struct E;
impl From<!> for E { fn from(x: !) -> E { x } }
fn f(never: !) {
<E as From<_>>::from(never);
}
```
The problem is that spontaneous decay of `never` makes the call require `E: From<()>` rather than `E: From<!>`. This problem disapears if we disable spontaneous decay.
This issue occured in normal code because of the `?` desugaring. However, the desugaring has been changed, and the new one does not reproduce this issue[^7].
### Attempt 4 (2020-11)
[attempt 4]: #attempt-4
- PR: https://github.com/rust-lang/rust/pull/79470
This was an attempt at "conditional decay", where the idea would be that we only decay `!` to `()` when necessary. This PR still broke a few crates and was deemed not good enough, particularly because it required too complex and hard to explain rules.
### Attempt 5 (2021-09)
[attempt 5]: #attempt-5
- PR changing the spontaneous decay rules: https://github.com/rust-lang/rust/pull/88804
- Design meeting: https://hackmd.io/9sTGmQ_VQ_mX_bdQhuqVRA
This attempt to work on `!` seems to have died out for similar reasons to the previous one:
- Unclear motivation of `!` and coercion behavior
- Complex rules needed to keep backwards compatibility
# Analysis
[analysis]: #analysis
Most of the issues look to be resolved. However, there is still an inherent conflict:
- Spontaneous decay is being depended on
- `Infallible` does not sponteneously decay, so making `Infallible = !` requires `!` to also not decay
So we can't both
- Not break code by keeping spontaneous decay (do we even want this? spontaneous decay is very confusing)
- Make `Infallible = !`
# Options
[options]: #options
For the next (2024) edition, I think the only thing that makes sense is to disable spontaneous decay (aka "always fallback to `!`"). This is the easiest behavior to teach and understand. This ideally achieves goals of limiting spontaneous decay and making it easy to understand (there is none!), while not breaking any code (opt-in, new edition)[^6]. For completeness there is the checklist:
1. ✅ Ideal for teachability & simplicity
2. ✅ No spontaneous decay
3. ✅ Does not break any code
4. ✅ Allows `Infallible = !`
- By itself, to actually make `Infallible = !` we need all editions to allow that
(our 5-th goal of not getting unbound inference variables in common code is satisfied by all proposals here, so I will not mention it)
For previous editions everything is much more complicated. Since we are bound by the backwards compatibility of `!` spontaneous decay *and* of `Infallible` not spontaneously decaying, there aren't any perfect options. Still, there are options:
1. Also fully disable spontaneous decay
1. ✅ Ideal for teachability & simplicity
2. ✅ No spontaneous decay
3. ❌ Breaks existing code which depend on the decay
4. ✅ Allows `Infallible = !`
2. Keep the current spontaneous decay rules (always decay)
1. ⚠️ Bad for teachability & simplicity, but not the worst
2. ❌ Spontaneous decay always
3. ✅ Does not break existing code
4. ❌ `Infallible` is not `!`
3. Keep the current spontaneous decay rules (always decay) but still make `Infallible = !`
1. ⚠️ Bad for teachability & simplicity, but not the worst
2. ❌ Spontaneous decay always
3. ❌ Breaks some code where `Infallible` spontaneously decaying causes problems
4. ✅ `Infallible = !`
4. Come up with a spontaneous decay rules which break no code and allow `Infallible = !`
1. ❌ Very bad for teachability & simplicity
2. ⚠️ Some spontaneous decay
3. ✅ Does not break existing code
4. ✅ Allows `Infallible = !`
# Proposal
[proposal]: #proposal
I think we need to commit to disabling spontaneous decay in the next edition. No matter what we decide for other editions, in my opinion, it's a good choice. It's a simple change that can be implemented quickly. That allows us to not block 2024 edition on any other decisions about the never type.
For previous editions I want to propose going with the option 1: fully disabling spontaneous decay everywhere. I think that the simplicity of the language rules and their teachability is very important. All other options seem significantly worse with this, but also with other goals too.
From what I can tell this was the original plan ([Niko's comment](https://github.com/rust-lang/rust/issues/35121#issuecomment-367872709)), which was then abondaned because of the issues described in the history section (all resolved now) and the fact that it can break `unsafe` code, which is scarry.
I believe that we can make a lint which would catch the unsafe code breaks[^8]. It's relatively easy to catch the fact that fallback goes into `mem::transmute` or `ptr::read`, etc.
Non-unsafe breaks are few and far between, from what I can tell in the discussions (although we'll have to remesure of course). And I want to argue that this falls into the "technically a breaking change, but allowed" category (you can always specify the `()` type explicitly). Still, this is a breaking change and we should look into warning people in advance (with lints and/or blog posts).
We have tried to tune the decay rules to not break any code, however this causes very complex and confusing rules, which also fallback to `()` most of the time.
I think it is crucial for the stabilization of `!` to disable the spontaneous decay, because that makes `!` a lot more intuitive to work with. I think that the spontaneous decay of `!` to `()` is so counterintuitive, that it's unreasonable to believe that people depended on it consciously. IMO it's a miracle that their code worked in the first place.
## Proposed next steps
- Adjust `#![feature(never_type_fallback)]` to never spontaneously decay `!` to `()`.
- That is, if the output type of a never-to-any coercion is unbounded (it cannot be inferred), then always assume `!` rather than `()`.
- Or add a new feature to that effect (like `#![feature(no_spontaneous_decay)]`).
- Enable (the equivalent of) `#![feature(no_spontaneous_decay)]` in Rust 2024.
- Add a future-incompat lint in all editions anywhere that this change when stabilized would make code fail to compile (e.g. the `match .. { x => Default::default(), y => panic!(), }` scenario).
- See: https://github.com/rust-lang/rust/issues/39216
- Add a lint in all editions anywhere that fallback would flow into scary places like `mem::transmute`, `ptr::read`, etc.
- See: https://github.com/rust-lang/rust/issues/66173
- Eventually enable (the equivalent of) `#![feature(no_spontaneous_decay)]` in all editions.
- We justify this as breakage allowed under RFC 1122 for ["underspecified language semantics"](https://github.com/rust-lang/rfcs/blob/master/text/1122-language-semver.md#underspecified-language-semantics), specifically "details of type inference may change".
- Stabilize `!`, alias `Infallible = !`.
---
**Mark that you're done reading when reaching this point.**
---
# Appendices
Here are some relevant, but not absolutely required, topics.
## Appendix A: another reason against spontaneous decay
[Appendix A]: #Appendix-A-another-reason-against-spontaneous-decay
Another reason why we want to disable spontaneous decay / fallback to `!`, noted [here](https://github.com/rust-lang/rust/issues/57012#issuecomment-452154912) by Taylor Crammer, is that it leads to better dead code detection:
```rust
let t = std::thread::spawn(|| panic!("nope"));
t.join().unwrap();
// this is dead code, but currently rust does not see it.
// `!` from the panic immediately decays to `()` and nothing
// in `t.join().unwrap();` shows that it's diverging
println!("hello");
```
## Appendix B: `From<!> for T`
[Appendix B]: #Appendix-B-Fromltgt-for-T
Since `!` can be coerced to any type, it would make sense to have the following `From` impl:
```rust
impl<T> From<!> for T {
fn from(never: !) -> T {
never
}
}
```
However, it conflicts with the identity impl when `T = !`.
Additionally adding such an impl after stabilization of `!` is a breaking change, because users can write their own conflicting impls.
~~Oli [believes](https://rust-lang.zulipchat.com/#narrow/stream/144729-t-types/topic/.60for.3CT.3E.20From.3C!.3E.20for.20T.60.3F/near/425877342) we can still add this impl as a builtin, ignoring the conflict with the identity impl.~~
Turns out we can't actually do that, ~~current type checker~~ the trait solver cannot support such an impl, since it is incoherent. (edit: errs)
## Appendix C: current `feature(never_type_fallback)`
[Appendix C]: #Appendix-C-current-featurenever_type_fallback
It is of note that currently `feature(never_type_fallback)` does not (fully) disable spontaneous decay. Instead it tries to still decay when it's required.
The behavior is documented in rustc code: [[link]](https://github.com/rust-lang/rust/blob/e919669d42dfb8950866d4cb268c5359eb3f7c54/compiler/rustc_hir_typeck/src/fallback.rs#L165), although it is quite complicated.
## Appendix D: blocks become never
[Appendix D]: #Appendix-D-blocks-become-never
When a block has a statement which has `!` type and there is no last expression (i.e. it ends in a statement) that block returns `!`, not `()`.
```rust
let a = {
loop {};
();
};
let b = {
loop {};
()
};
// a: !
// b: ()
```
This is not terribly bad, although a bit confusing and, IMO, unnecessary.
Maybe we can also disable this in the next edition? That is, maybe we can make them both `()` in the next edition, to make the rules simpler & more intuitive (the result of a block will always be the last expression or `()` if there is no expression).
The current rules are not *that* complex (at least to my knowledge) to warrant a breaking change, but would be nice to get rid of this special case at least in the next edition.
Note that this has an implication on `return;`:
```rust
let x = if cond {
y
} else {
// currently: allowed (the else block has a diverging statement,
// so it's made to result in ! and then coerced to y's type)
//
// if we remove that rule: error (expected `typeof(y)`, found `()`)
return;
};
let x = if cond {
y
} else {
// you'll have to write this instead
return
}
```
I was confused for years, why `return;` is allowed in such cases, and even though now I know the rules it's still irking me ^^'
Either way, we must document the behavior: https://github.com/rust-lang/reference/issues/1033.
**N.B.** This is very explicitly not part of the current proposal and should be viewed as a future possibility.
## Appendix E: the hack to get stable never
[Appendix E]: #Appendix-E-the-hack-to-get-stable-never
You can exploit the fact that `fn() -> !` pointers are stable and the fact that `FnOnce` is stable (in some capacity) to just `<fn() -> ! as FnOnce<()>>::Output`. In reality you need to create a separate trait, because you can't specify an `FnOnce` bound without specifying `Output` to a specific type.
Still, this is fairly easy ([play](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=879fc3908f4e18fdcbef5e87d166a060)):
```rust
trait F {
type Out;
}
impl<T> F for fn() -> T {
type Out = T;
}
type Never = <fn() -> ! as F>::Out;
fn test_never_to_any<A>(never: Never) -> A {
never
}
```
There is an existing (joke?) implementation: lib.rs/never-say-never.
I'd say that this is clearly a hack and is outside of our stability guarantees.
This is tracked in [#58733](https://github.com/rust-lang/rust/issues/58733) (although I don't think there is anything to be done, but to stabilize the never type already).
## Appendix F: function pointers allow impls to mention `!`
[Appendix F]: #Appendix-F-function-pointers-allow-impls-to-mention-
Similary to the previous one, you can implement traits for `fn() -> !` *and* `fn() -> Infallible`, depending on the fact that those are different types ([play](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9b09bfaabc94e2d6b06b7906678d7011)):
```rust
trait Trait {}
impl Trait for fn() -> ! {}
impl Trait for fn() -> Infallible {}
```
We can never make `Infallible` into an alias for `!` without breaking this.
I'd assume that this does not happen in practice -- no crater runs showed breakage due to *this* & it's fairly uncommon to implement traits for specific function pointers like this.
## Appendix G: bad desugaring of `?`
[Appendix G]: #Appendix-G-bad-desugaring-of-
As it turns out, `?` actually skews inference:
```rust
// currently: compiles (T = ())
// with this proposal: compiles (T = !)
Err(())?;
// currently: compiles (Self = ())
// with this proposal: fails (Self = !, which does not implement the trait)
serde::Deserialize::deserialize(deserializer)?;
```
This is because `?` desugars to a match with a return:
```rust
// Simplified: current version uses `Try`, `ControlFlow`, ...
match expr {
Ok(val) => val,
Err(err) => return Err(err),
}
```
This unifies type of `val` with type of `return` (which is `!`) which causes fallback to kick in. Ideally `?` would not do that (this is clearly a bug in the desugaring).
Issue: https://github.com/rust-lang/rust/issues/51125.
PR with a simple fix: https://github.com/rust-lang/rust/pull/122412.
## Appendix H: breakage from disabling spontaneous decay
[Appendix H]: #Appendix-H-breakage-from-disabling-spontaneous-decay
There *is* real breakage from disabling sponteneous decay. It happens when something like `panic!()` is passed to a generic function, which would work with `()`, but not `!`:
```rust
struct X;
impl From<X> for () { ... }
fn generic(_: impl From<X>) {}
generic(panic!());
```
The fix is generally to just replace the panic with a unit:
```rust
panic!();
generic(());
```
A possibly more common example involves a closure:
```rust
struct X;
impl From<X> for () { ... }
fn call<R: From<X>>(_: fn() -> R) {}
generic(|| panic!());
```
And is fixed similarly:
```rust
generic(|| {
panic!();
()
});
```
(alternatively you can explicitly specify the type via turbofish)
Real example: [[comment]](https://github.com/rust-lang/rust/issues/48950#issuecomment-380255783).
<!---------------------------->
[^1]: [Tracking issue for promoting ! to a type (RFC 1216)](https://github.com/rust-lang/rust/issues/35121) was opened on 2016-07-29
[^3]: A `loop {}` without `break`s inside is `!`-typed, but one with `break`s will have the same type as the values passed to the `break`s
[^4]: [[comment]](https://github.com/rust-lang/rust/pull/44174#discussion_r135983679)
[^5]: Not all breaking changes are created equal; we'll still have to support the old code using the old edition
[^6]: It might not have ideal interopability with old editions, since macros expanded in a new edition, but written with spontaneous decay in mind, will break (and vice-versa). But "macros which depend on spontaneous decay of `!`" seems like a very niche usecase (And again: this is not *breaking* any existing code, only adding theoretical issues with cross edition interactions). There is also precedent with NLL: [[comment]](https://github.com/rust-lang/rust/issues/35121#issuecomment-767567492).
[^7]: The new desugaring passes `Result<!, E>`, instead of `E` to a generic function (`Try::from_residual`), so since `!` does not apear in non-generic code, there is no place for spontaneous decay to happen. ([zulip discussion](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Never.20type.20and.202024.20edition/near/420928920))
[^8]: I did most of the implementation, but then stopped because it was unclear what T-lang actually wants out of the lint -- which breaks we want to do.
---
# Discussion
## Attendance
- People: TC, tmandry, Nadri, scottmcm, Waffle, Lukas Wirth, Josh, pnkfelix, nikomatsakis, Oleksandr Babak
## Meeting roles
- Minutes, driver: TC
## `!` or `?Fresh`
scottmcm: it mentions:
> A lot of things that concern control flow have ! type. For example: return, break, continue, panic!(), loop{}[2].
Do they have literally `!` type, or unbound-type-variable type?
waffle: Right now they do actually have `!` type, to the best of my knowledge.
## Note on `Infallible`
scottmcm: There's a bunch of implementations in the wild that use `Infallible` today. The biggest reason to wish for the merge is that it's not a breaking change for those libraries -- including `core`! -- to change to real-`!` instead. As well as all the error types that have implemented `MyError: From<Infallible>` to work with `?`, where it would be nice for things to immediately work with `!` as well once that's stable, rather than needing the ecosystem to go add a bunch more identical `From`s.
## If we make `Infallible = !`, then "Appendix B" already applies
nikomatsakis: A point that was raised in the past is that there are already existing `impl From<Infallible> for MyType` impls out there. If we alias `Infallible = !`, then it is already a coherence failure to add an impl like `impl<T> From<!> for T`. Discussion around this is what led us to the current "reservation" system, which was intended to ensure that we *can* create the blank impl *if* we had specialization. I forget exactly what case it was trying to prevent.
NM: Whether we can add this conflicting impl is probably an orthogonal point.
pnkfelix: This feels like a major point.
Josh: We don't have that problem impl. We'd like to add `impl From<!> for T` but we can't because it'd conflict with `impl From<T> for T`.
## No "spontaneous decay" = "always fallback to `!`", correct?
nikomatsakis: The terminology of "spontaneous decay" is new to me, but I believe that in terms of the type system implementation, it means "if we convert `!` to a type variable, that type variable falls back to `!`". One of the reasons I am not super keen on this is code like the following
```rust
trait Deserialize {
fn deserialize(input: &str) -> Self;
}
// Reasonable
impl Deserialize for () {
fn deserialize(input: &str) -> Self {
()
}
}
// Reasonable
impl Deserialize for ! {
fn deserialize(input: &str) -> Self {
panic!()
}
}
fn main(input: Option<&str>) {
// In past at least, resulted in `x: !` which would mean a guaranteed panic.
// I'd prefer an error for unconstrained type variable.
let x = match input {
None => return, // has type `!`
Some(x) => Deserialize::deserialize(input),
};
}
```
Do we have a plan for how to address cases like this?
With `never_type_fallback` today, it appears to generate `()`. [playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=5ebd0535c1e47d5ab239c90e7cf845ba)
I guess this is Appendix G.
Waffle: Yes, the proposal is to always fallback to `!`. It would be unfortunate to issue an unbound error here.
scottmcm: This gets to my control-flow lowering question. Something like that may be a way around these sort of problems.
Josh: ...
nikomatsakis: The aligns with what I'd expect.
Nadri: This would make it work less like a normal type. IS there a sensible use of `Infallible` where changing this would be surprising?
Waffle: There are two perspectives here. One is seeing `!` as a type. The other is seeing the control-flow effects of that `return`. Not sure what to do with this contradiction.
scottmcm: The backcompat restriction is on functions that return `!` directly. I'm wondering if we could treat the `-> !` case somewhat specially.
NM: I agree with Waffle. I came to the same conclusion awhile back that there is this tension.
NM: No other type has fallback (except integers, which are weird), so there is specialness here. If we just treated `!` as another type, much of the concern goes away, but that raises some ergonomics questions.
NM: I've outlined some scenarios here of cases that may feel similar to the user but may be different to the compiler:
```rust
let x = match input {
None => return,
Some(x) => ...
};
let x = match input {
None => panic!(),
Some(x) => ...
}
let x = match input {
None => my_panic(),
Some(x) => ...
}
let x = match input {
None => wrapper(|| return),
Some(x) => ...
}
let x = match input {
None => wrapper(|| my_panic()),
Some(x) => ...
}
fn my_panic() -> ! { }
fn wrapper<R>(_: impl FnOnce() -> R) -> R { }
```
NM: My recollection is that by not falling back to `()`, some of these cases stop working.
Waffle: The way that I see the always fallback to `!` behavior is that it's equivalent to not having fallback and only coercing to `!` when required. So in that sense, it's the same as other types.
nikomatsakis: I feel like this example is one where we would *either* not coerce *or* give an unconstrained error (though after having said this, Niko is rethinking, it's just kind of hard to draw a comparison I guess). I guess Waffle's point is, if that `return arm` returned `Infallible`, it would behave the same (but, on the other hand, we wouldn't be able to have *any* type in the other arm, specifically because).
Josh: If we generate scenarios where code panics that didn't, that is something we probably need to address. If it's that it only causes compiler failure, that's probably not blocking.
TC: This is a change to type inference, and any change to type inference can cause arbitrary changes to runtime behavior. This is true even for the changes we characterize as minor in RFC 1122.
scottmcm: nit: I wouldn't focus on the panic! in the example -- it might be `Err` or something instead. Worst is `transmute` cases, since going from `transmute::<_, ()>` to `transmute::<_, !>` is much much much worse than panic -- and `!` is ZST so the size check doesn't notice.
Waffle: It seems like a fairly narrow case where behavior might change. Probably this doesn't happen too often.
Nadri: can we experiment with the control flow thing above?
Nadri: How did we pick up on the `objc` case, and can we find other cases like that?
NM: In that case, code started to crash, and people investigated. We have tried to lint lints for this and it's challenging. The inference chain doesn't necessarily all happen in a single function.
NM: On the question of experimentation, we tried very hard on this, but perhaps we were operating under too many constraints.
NM: For example, what if we only allow coercions from `!` to all types?
```rust
let x = from(return);
fn from<T>(t: T) { }
```
NM: Under my version I realized this would not compile. So I added heuristics that turned out to be complex.
https://gist.github.com/nikomatsakis/7a07b265dc12f5c3b3bd0422018fa660
## Does this need to happen on all editions?
pnkfelix: The text says "By itself, to actually make Infallible = ! we need all editions to allow that"
pnkfelix: I interpret that as saying "if any edition makes Infallible = !, then *all* editions need to make Infallible = !"
pnkfelix: is that the correct interpretation?
pnkfelix: assuming it is the correct interpretation... why is that the case? E.g. could we not have `!` denote one type (that is interchangable with Infallble) for editions >= 2024, and ... well, `!` just won't denote a first-class type on editions < 2024, right? I feel like I'm missing something about why this equivalence cannot be edition-specific, if the `!` type itself is already (potentially?) edition-specific.
pnkfelix: (Or are all those bullet points about pre-2024 edition implicitly including a goal that we stabilize `!` as a first-class type for *all* editions?)
pnkfelix: (maybe there is a concern re passing around values with types like `fn () -> !` and such on old editions)
scottmcm: I think the place this gets hard is when you're using `core` traits like the `TryFrom` blanket that has `Error = Infallible`. That probably has to be the same type across inference that can involve multiple editions.
Waffle: The questions: Can we stabilize `!` only on one edition?
Waffle: I don't think so; it doesn't seem feasible to me.
pnkfelix: Could we make `!` one type in Rust 2024 and another type in other editions?
tmandry: We could treat it as a type alias in newer editions
Nadri: We could allow `!` as a syntax in more places in Rust 2024 and not previous editions. Then there's a discussion about changing the behavior of it. And there's the problem of equating it with `Infallible` which I think has to be done in all editions at once.
scottmcm: It might be scary to do this based on the span.
## Coercing all uninhabited types to `!`
tmandry: Rather than `Infallible = !`, how much would we gain by making all uninhabited types _coercible_ to `!`? This would also apply to existing empty enums in the ecosystem that are used for the same purpose.
nikomatsakis: Also, how we would we do it. In the past I was semi-unkeen on this but I've softened, especially as we gained the idea of "privately uninhabited". We might just do it for e.g. empty enums specifically, even.
Nadri: only empty enums please :D
scottmcm: Anywhere that coercions don't apply because we're doing trait resolution would be a problem here. Because of generics, many places are not coercion sites.
Waffle: `Infallible` is often used inside of a `Result`, and it's not going to coerce here.
nikomatsakis: e.g., `Result<X, Infallible>` cannot be coerced to `Result<X, !>` (at least, not today).
tmandry: It makes we wonder if we could solve this problem independently. Maybe would could come up with rules to allow us to coerce here.
Waffle: It seems undesirable to add special behaviors to all uninhabited types here.
nikomatsakis: In the same way that `()` feels distinct from `struct Foo`, I feel like it's prob useful to distinguish `!` from empty enums at least in *some* cases.
Nadri: remember that `(T, !)` is not a ZST (it can store a `T` today), so definitely I wouldn't want to do anything about *all uninhabited types*.
NM: I agree with Waffle that it's useful to be able to distinguish "the canonical empty type" from other, newtype'd flavors of it. But I also agree with tmandry that people have used empty enums in places where they would have wanted to use the never type. But there would be weird interactions in treating all uninhabited types in this way, I could *maybe* imagine some special case around empty enums but not other kinds of uninhabited.
tmandry: I'd be okay with restricting to empty enums, perhaps even requiring an attribute to be added to the enum to make it behave more like `!`.
## Pondering: lowering to control flow?
scottmcm: Since the only thing we need to keep working for back-compat is `-> !`, is there a way we could detect that somehow and treat it like control flow, like how we changed the `&&` desugaring recently for let chains? Were `let x = if c { expr } else { returns_never() }` to not actually have a value edge back from the `else` block, maybe that'd let us dodge the need for decay?
Nadri: to be precise, this has to do with never_to_any, not decay I think.
scottmcm: I guess this is harder than grammar things, since we'd have to do it in the middle of name-resolution-and-type-checking since that's when we'd find out things are `-> !`. But we only have to support concrete `-> !`, at least, I think?
## Appendix H and "The fix is generally to just replace the panic with a unit"
Josh: The most common way I'd expect this to happen: `code_in_progress(xyz, todo!())`.
Josh: It wouldn't be satisfying to change the `todo!()` to `()`.
Waffle: The only way this changes is if you're passing this to a function with a generic with a trait bound that is implemented for `()` but not `!`.
NM: This is a good example of where the spontaneous decay is surprising.
scottmcm: This is already an error, for example: <https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3ef16be86bddfb9d4e609aac2a099a61>
```rust
fn code_in_progress(x: &str, y: impl Into<String>) {}
fn main() {
let xyz = "asdf";
code_in_progress(xyz, todo!());
//~^ ERROR: the trait bound `String: From<()>` is not satisfied
}
```
NM: Even today, this works less often than I wish it did.
scottmcm: This doesn't compile today, but arguably we have the syntax-level knowledge to know that the call is comhpletely unreachable, and thus could potentially skip the need to determine a type. (But then we're at "are types checked in dead code?", where I think we've said they should before.)
```rust
fn code_in_progress(x: &str, y: impl Into<String>) {}
fn main() {
let xyz = "asdf";
code_in_progress(xyz, return todo!());
}
```
nikomatsakis: Consider:
```rust
fn code_in_progress(x: &str, y: impl Into<String>) {}
fn main() {
let xyz = "asdf";
code_in_progress(xyz, wrapper(|| todo!()));
}
```
## Spontaneous decay, why do we... care?
nikomatsakis: I see the argument for unifying `Infallible` and `!`. I don't fully understand the reason to fallback to `!`. I'm sure there are advantages to it, I'd like to know why it's useful though.
scottmcm: One I recall is something like `Err(e)?`, which today is `()` and that feels weird to people because it's unreachable. Of course, adding `throw e` would be the nice way around that particular case.
scottmcm: `Ok(v)` giving you `Result<T, ()>` instead of `Result<T, !>` makes a major difference.
Nadri: because you could have a `let x: Infallible` that unexpectedly turns into a `()` when you try to use it. Waffle had examples IIRC. Or do you mean having fallback at all?
nikomatsakis: I suppose...
```rust
let x: ! = ...;
let y = Some(x); /* infers to `Option<()>` ?
```
...?
TC: cramertj described some motivation for this as follows:
> As for why we should make this change, [the RFC lists a couple of motivating factors](https://github.com/rust-lang/rfcs/blob/master/text/1216-bang-type.md#motivation), the most relevant of which is (IMO) better dead-code detection, such as in cases like this:
>
```rust
let t = std::thread::spawn(|| panic!("nope"));
t.join().unwrap();
println!("hello");
```
>
> > Under this RFC: the closure body gets typed ! instead of (), the unwrap() gets typed !, and the println! will raise a dead code warning. There's no way current rust can detect cases like that.
>
> This change makes it possible to catch more bugs via dead-code warnings, expand the reachability analysis of the compiler allowing for smarter optimizations, and helps to provide "more correct" types like `Result<(), !>` rather than `Result<(), ()>` in more places (which may turn previously manual user assertions into no-ops). It also helps introduce the user to the concept of `!` by giving a "more correct" return type to diverging expressions.
NM: Yes, that's a good one. When I see threats to reliability I get very concerned.
Waffle: There are there canonical examples:
1. The case above.
2. You can't make `Infallible` into the never type.
3. Intuition, in particular error messages
nikomatsakis: [Another example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=41da5343af276d399b9b08e6400ab297):
```rust
#![feature(never_type)]
fn main() {
let x: Option<!> = None;
let y: Option<_> = x.map(|x| x);
println!("sizeof={}", std::mem::size_of_val(&y)); // prints 1
let z: Option<!> = x.map(|x| x);
println!("sizeof={}", std::mem::size_of_val(&y)); // prints 0
}
```
nikomatsakis: Example that failed to compile when we unified `Infallible = !` and we kept "fallback to `()`":
```rust
struct E;
impl From<!> for E { fn from(x: !) -> E { x } }
fn f(never: !) {
<E as From<_>>::from(never);
// never: gets coerced to `()`
}
```
## Next steps
TC: Where do we want to go from here?
NM: This was a fantastic document. I do want to unblock this going forward. I do have reliability concerns. It seems we need to explore whether we want to consider more complicated rules.
NM: I'm curious who is worried about this match example:
```rust
trait Deserialize {
fn deserialize(input: &str) -> Self;
}
// Reasonable
impl Deserialize for () {
fn deserialize(input: &str) -> Self {
()
}
}
// Reasonable
impl Deserialize for ! {
fn deserialize(input: &str) -> Self {
panic!()
}
}
fn main(input: Option<&str>) {
// In past at least, resulted in `x: !` which would mean a guaranteed panic.
// I'd prefer an error for unconstrained type variable.
let x = match input {
None => return, // has type `!`
Some(x) => Deserialize::deserialize(input),
};
}
```
nikomatsakis: I semi-block the proposal because of the above -- I find it a blow to reliability to fallback to `!`. I'd like to see this addressed. I'm curious what other people think.
scottmcm/tmandry: can we use lints?
TC: NM, is there anything the edition can help with here on your concern?
NM: Yes, e.g. with my proposal, we could give a hard error on fallback to `()`.
Waffle: One of the main points of this proposal is that a solution that will make everyone happy is going to be very complex. So my proposal is that doing something simple and having lints to help the unhappy people may be the best path. What do people think about that?
pnkfelix: The transmute examples are really unfortunate. I don't know what to do about that. Maybe a lint would help.
NM: If we turned my gist into a lint, that would catch those examples, as I recall.
scottmcm: The big takeaway for me on this document is understanding how the spontaneous decay is really a different thing. I may be willing to risk some of the UB cases to get rid of that.
NM: Waffle, I'd be up to talk this over later.
(The meeting ended here.)
## Allowing overlapping impls for `!`
tmandry: Strawman proposal: This would literally compile:
```rust!
impl<T> From<!> for T {}
```
We treat every trait item that depends on having a value of `!` as having an implicit `where T: Inhabited`. Also, we make an exception to the overlap check for `!` and possibly other publicly-uninhabited types.
This would still require traits to opt in to the impl, rather than doing something probably-unwise like automatically implementing traits for `!`.
Nadri: would this be consistent? Sounds like we could need specialization
tmandry: The intuition is that any item we allow overlap with wouldn't be reachable. We probably couldn't make it work for traits which have associated types.
Nadri: similar to how we allow overlap for marker traits maybe? I *think* we do at least.
tmandry: Exactly.. in the `!` case some traits effectively have no items, so they act more like marker traits.
## Topic
name: Question/comment.