# fallback and `!`
## Background
* We introduced the `!` type in RFC XXX
* Currently, for "abrupt" expressions like `return` and `break`, result is a type inference variable that (if otherwise unconstrained) falls back to `()`
* RFC proposed to change that to `!`
* However, it was found that this change caused a certain amount of breakage in practice
* some of this manifested as failed compilations
* but other cases (notably the `objc` crate) resulted in silent crashes
## When does the fallback mechanism apply
* the type of an expression like `panic!()` is an inference variable
* if that variable winds up being unconstrianed, it will "fallback" to either `()` (today) or `!` (proposed)
* it is unusual for variables to be fully unconstrained but certainly possible, especially around dead code
Example:
```rust
fn foo() {
let x: _ = panic!();
bar(&x);
}
fn bar<T: Debug>(t: T) { }
```
The type of `x` here is an unconstrained inference variable `?X` -- the only *constraint* is that whatever `?X` winds up being, it must implement `Debug`.
## Arguments in favor of changing the fallback
The `!` type is more likely to be implemented and to integrate with `impl Trait`,
[as scottmcm pointed out here][mcm]:
[mcm]: https://github.com/rust-lang/rust/pull/65355#issuecomment-550000330
```rust
pub fn demo() -> impl std::error::Error {
unimplemented!()
}
```
## Examples where problems arise
### Compilation failures
I'm not sure if we saw in the wild, but it's possible to have a compilation failure if you have fallback and pending trait obligations that `!` cannot satisfy:
```rust
```
### Runtime errors
Code that uses `mem::zeroed` or `mem::uninitializd` can sometimes be "tricked"
into synthesizing a `!` value. This presently results in a lint and a runtime panic, as [Centril notes here][Centril1]:
[Centril1]: https://github.com/rust-lang/rust/pull/65355#issuecomment-550193109
> The former might not result in an error. It does however result in a warning (`invalid_value`) as well as a run-time panic ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d0f73397e2d819d97cbc5f88fc7eed43)):
>
> ```
> warning: the type `!` does not permit zero-initialization
> --> src/main.rs:8:13
> |
> 8 | std::mem::zeroed()
> | ^^^^^^^^^^^^^^^^^^
> | |
> | this code causes undefined behavior when executed
> | help: use `MaybeUninit<T>` instead
> |
> = note: `#[warn(invalid_value)]` on by default
> = note: The never type (`!`) has no valid value
>
> Finished dev [unoptimized + debuginfo] target(s) in 1.12s
> Running `target/debug/playground`
> thread 'main' panicked at 'Attempted to instantiate uninhabited type !', /rustc/1423bec54cf2db283b614e527cfd602b481485d1/src/libcore/mem/mod.rs:461:5
> note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
> ```
>
> As you can see from ^, there's no UB in sight.
### The "Deserialize" pattern (XXX rust issue number ([issue 39297?](https://github.com/rust-lang/rust/issues/39297))
One pattern that was found worked like this:
```rust
fn foo() -> Result<(), ()> {
let x = try!(Deserialize::deserialize())?
println!("{:?}", x);
Ok(())
}
```
when `try!` is expanded, the result is
```rust
let x = match Deserialize::deserialize() {
Ok(v) => v, // Ok match arm has type `?OK`
Err(err) => return err.into(), // Err match arm has type `?ERR`
}
```
The problem here is that the type of the both match arms are inference variables, let's call them `?OK` and `?ERR`. Those two variables are constrained to be the same, but neither of them are constrained to anything in particular. At fallback point, `?ERR` falls back to `!`, and hence `?OK` also falls back to `!`. Therefore, the final type of `x` is inferred to `!` -- but deserializing a `!` value doesn't make sense -- in particular, the deserialize trait was not (at the time) implemented for `!`, so we got an error.
Had deserialize been implemented for `!`, the only possible behavior would be to panic, and hence this code would go from deserializing a `Result<(), ()>` (which could well succeed) to deserializing a `Result<!, ()>` (which panics). (In the particular case where this code was found, though, the value was known to be the `Err` variant so that would not in fact have happened.)
Nonetheless, it was surprising that code which is **not obviously dead** winds up with the type `!` (that is, the variable `x`).
### objc crate
The core of the objc crate error is the same pattern as the `Deserialize` error, as [SSheldon noted here][ss1].
[ss1]: https://github.com/rust-lang/rust/pull/65355#issuecomment-550117628
```rust
let _ = if false {
panic!("panic")
} else {
mem::zeroed()
};
```
However, the specific case involved a `fn() -> !` value, [as SimonSapin clarified here][ss2]:
[ss2]: https://github.com/rust-lang/rust/pull/65355#issuecomment-550214930
> \[objc\] [transmutes](https://github.com/SSheldon/rust-objc/blob/735816219676ad2b569163315cd23b002d1492ea/src/message/mod.rs#L126-L128) a pointer to `fn(…) -> R` and calls it. `R` is a generic type parameter that is inferred, and in the cases discussed here affected by the fallback change.
## Data on expected breakage
XXX links to old crater runs and summaries of their results
XXX objc fallout results from thread
## Mitigation options
One thing to consider is whether we can **mitigate** the fallout through a warning period. The question is how one would design a suitable lint and how precise it would be. If the lint is overly coarse, we might make it **opt-in** (i.e., allow by default).
Option 1: use "taint tracking" on the `()` value that results from fallback. This is what we attempted at first. It was complex and we are not keen to attempt it again.
Option 2: when falling back a variable `?X` to `()`, search the outstanding trait obligations and see whether any of them reference `?X`. If so, issue a lint warning -- or perhaps get more precise, for example by "evaluating" the trait obligation with `()` and `!` to see if the result differ. (To check: Would this actually capture the "semantic" violations from cases like objc?)
## Options
* Stabilize `!` type but leave fallback *temporarily* unresolved
* this doesn't resolve the question, so it's really rather orthogonal to the document, but it may be worth considering regardless
* the [main concern](https://github.com/rust-lang/rust/issues/58184#issuecomment-460697760) when this was [previously proposed](https://github.com/rust-lang/rust/issues/58184) was that we would have insufficient motivation to pursue a change to fallback (which does seem plausible, unless someone commits to seeing it through)
* Leave the fallback as `()`.
* Transition to `!`:
* Can we do some sort of warning period?
* How much do we have to prepare the ecosystem?
* Edition boundary
* no code breaks: good!
* but it is complex for us to manage and may result in surprising interactions
* presumably the edition of the `return` or `break` statement would be used to determine the fallback
* depending how precise we are, we may see code breakage anyway