Skip to content

Adding generics affect whether code has UB or not, according to Miri #146803

@theemathas

Description

@theemathas

I tried this code:

enum Never {}

#[repr(u64, C)]
#[allow(dead_code)]
enum Thing<T> {
    Never(T) = 0,
    Inhabited = 1,
}

fn main() {
    assert_eq!(size_of::<Thing<Never>>(), 8);
    let data = 0u64;
    // Discriminant is 0, so we should have Thing::Never
    let thing_ref = unsafe { &*(&raw const data).cast::<Thing<Never>>() };
    // This is fine
    concrete(thing_ref);
    // This has UB
    generic::<Never>(thing_ref);
}

fn concrete(x: &Thing<Never>) {
    match x {
        &Thing::Inhabited => {
            println!("Inhabited");
        }
        _ => {
            println!("Never");
        }
    };
}

fn generic<T>(x: &Thing<T>) {
    match x {
        &Thing::Inhabited => {
            println!("Inhabited");
        }
        _ => {
            println!("Never");
        }
    };
}

I expected the concrete and generic::<Never> calls to behave identically. Instead, Miri detects UB only for the generic::<Never> call:

error: Undefined Behavior: read discriminant of an uninhabited enum variant
  --> src/main.rs:33:11
   |
33 |     match x {
   |           ^ Undefined Behavior occurred here
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `generic::<Never>` at src/main.rs:33:11: 33:12
note: inside `main`
  --> src/main.rs:18:5
   |
18 |     generic::<Never>(thing_ref);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

It seems that, in generic, T is treated as inhabited, even if the T is later set to Never (which makes the type uninhabited in concrete). Whether the Thing::Never variant is inhabited then affects whether the enum discriminant is checked in the MIR.

Note that, when I tried executing the program (in either debug mode or release mode), it printed Inhabited, then printed Never. That is, the difference between the two functions can actually be observed outside of Miri.

Probably related to #142394 and #146590, which involves uninhabited types affecting MIR generation.

See also #146430, where generics can affect MIR-building.

Meta

Reproducible on the playground with rust version 1.92.0-nightly (2025-09-19 0be8e16088894483a701) and Miri version 0.1.0 (2025-09-19 0be8e16088)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlA-exhaustiveness-checkingRelating to exhaustiveness / usefulness checking of patternsA-miriArea: The miri toolA-patternsRelating to patterns and pattern matchingC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem teamneeds-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions