Expand description

The never_type_fallback_flowing_into_unsafe lint detects cases where never type fallback affects unsafe function calls.

§Never type fallback

When the compiler sees a value of type ! it implicitly inserts a coercion (if possible), to allow type check to infer any type:

// 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>(never: !) -> T { ... }

While it’s convenient to be able to use non-diverging code in one of the branches (like if a { b } else { return }) this could lead to compilation errors:

// this
{ panic!() };

// gets turned into this
{ absurd(panic!()) }; // error: can't infer the type of `absurd`

To prevent such errors, compiler remembers where it inserted absurd calls, and if it can’t infer their type, it sets the type to fallback. { absurd::<Fallback>(panic!()) };. This is what is known as “never type fallback”.


fn main() {
    if true {
        // return has type `!` which, is some cases, causes never type fallback
    } else {
        // `zeroed` is an unsafe function, which returns an unbounded type
        unsafe { std::mem::zeroed() }
    // depending on the fallback, `zeroed` may create `()` (which is completely sound),
    // or `!` (which is instant undefined behavior)



Due to historic reasons never type fallback was (), meaning that ! got spontaneously coerced to (). There are plans to change that, but they may make the code such as above unsound. Instead of depending on the fallback, you should specify the type explicitly:

if true {
} else {
    // type is explicitly specified, fallback can't hurt us no more
    unsafe { std::mem::zeroed::<()>() }

See Tracking Issue for making ! fall back to !.