rust-lang / rust-lang/rust

Strange precise closure capture behavior with uninhabited types and an empty `match`

Open
#157,243 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-closures A-patterns C-bug T-lang T-opsem
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

enum Never {}

fn make_closure(x: Never) -> impl Fn() {
    move || match x {}
}

fn helper<F: Fn()>(_: impl Fn(Never) -> F) -> F {
    unsafe { std::mem::MaybeUninit::uninit().assume_init() }
}

fn conjure_closure() -> impl Fn() {
    helper(make_closure)
}

fn main() {
    assert_eq!(0, size_of_val(&conjure_closure())); // UB here in edition 2018
    assert_eq!(1, size_of_val(&Some(conjure_closure())));
    conjure_closure()(); // UB here in edition 2021+
}

On edition 2021 or later, running the program in Miri results in both assertions passing, but UB only occurring after the closure is called:

error: Undefined Behavior: entering unreachable code
 --> src/main.rs:4:19
  |
4 |     move || 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: stack backtrace:
          0: make_closure::{closure#0}
              at src/main.rs:4:19: 4:20
          1: main
              at src/main.rs:18:5: 18:24

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

On edition 2018, running the program in Miri results in UB as soon as an instance of the closure is created:

error: Undefined Behavior: constructing invalid value of type {closure@src/main.rs:4:5: 4:12}: at .<captured-var(x)>, encountered a value of zero-variant enum `Never`
 --> src/main.rs:8:14
  |
8 |     unsafe { std::mem::MaybeUninit::uninit().assume_init() }
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: stack backtrace:
          0: helper::<{closure@src/main.rs:4:5: 4:12}, fn(Never) -> impl Fn() {make_closure}>
              at src/main.rs:8:14: 8:59
          1: conjure_closure
              at src/main.rs:12:5: 12:25
          2: main
              at src/main.rs:16:32: 16:49

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

error: aborting due to 1 previous error

It appears that, in edition 2021 or later, due to closure precise captures the closure move || match x {} does not capture the variable x. Even though it does not capture x, it is nevertheless allowed to use the information that the value x "exists" inside the closure, to cause UB. On the other hand, it seems that because the type Never was not captured, the closure is considered to be inhabited by the compiler, so Option<Closure> has size 1.

This behavior seems strange, although I'm unsure if this is a bug.

cc @Nadrieril @RalfJung

Meta

Reproducible on the playground with version 1.98.0-nightly (2026-05-31 14210df0e27ccd7d9e6a)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied src/main.rs reproduction and run it under Miri for editions 2018 and 2021 or later. Read the Rust 2021 disjoint-capture documentation, then compare closure capture, layout, and empty-match behavior; done means establishing whether the behavior is intended or requires a compiler or reference change.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.