!
!
type in RFC XXXreturn
and break
, result is a type inference variable that (if otherwise unconstrained) falls back to ()
!
objc
crate) resulted in silent crashespanic!()
is an inference variable()
(today) or !
(proposed)Example:
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
.
The !
type is more likely to be implemented and to integrate with impl Trait
,
as scottmcm pointed out here:
pub fn demo() -> impl std::error::Error {
unimplemented!()
}
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:
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:
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):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.
One pattern that was found worked like this:
fn foo() -> Result<(), ()> {
let x = try!(Deserialize::deserialize())?
println!("{:?}", x);
Ok(())
}
when try!
is expanded, the result is
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
).
The core of the objc crate error is the same pattern as the Deserialize
error, as SSheldon noted here.
let _ = if false {
panic!("panic")
} else {
mem::zeroed()
};
However, the specific case involved a fn() -> !
value, as SimonSapin clarified here:
[objc] transmutes 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.
XXX links to old crater runs and summaries of their results
XXX objc fallout results from thread
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?)
!
type but leave fallback temporarily unresolved
()
.!
:
return
or break
statement would be used to determine the fallback