rust-lang/rust · error

non-`async`/`gen` closure body turned `async`/`gen` during l

Error message

non-`async`/`gen` closure body turned `async`/`gen` during lowering

What it means

This `panic!` fires in `closure_movability_for_fn` when a closure's `coroutine_kind` is one of the `CoroutineKind::Desugared` variants (Gen/Async/AsyncGen) but the closure is being lowered through the plain-closure lowering path that is only valid for `Coroutine` or `None`. Desugared coroutine kinds (`async { }`, `gen { }`) are routed through `lower_expr_coroutine_closure`; if one reaches `closure_movability_for_fn`, the dispatch in the caller is wrong. It is a hard ICE indicating a logic error in the lowering dispatcher.

Source

Thrown at compiler/rustc_ast_lowering/src/expr/closure.rs:270

        &mut self,
        decl: &FnDecl,
        fn_decl_span: Span,
        coroutine_kind: Option<hir::CoroutineKind>,
        movability: Movability,
    ) -> hir::ClosureKind {
        match coroutine_kind {
            Some(hir::CoroutineKind::Coroutine(_)) => {
                if decl.inputs.len() > 1 {
                    self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span });
                }
                hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability))
            }
            Some(
                hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)
                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)
                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _),
            ) => {
                panic!("non-`async`/`gen` closure body turned `async`/`gen` during lowering");
            }
            None => {
                if movability == Movability::Static {
                    self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span });
                }
                hir::ClosureKind::Closure
            }
        }
    }

    fn lower_closure_binder<'c>(
        &mut self,
        binder: &'c ClosureBinder,
    ) -> (hir::ClosureBinder, &'c [GenericParam]) {
        let (binder, params) = match binder {
            ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]),
            ClosureBinder::For { span, generic_params } => {
                let span = self.lower_span(*span);

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Report to the rustc ICE tracker with a reproducer using `#![feature(gen_blocks)]` / `async` closures — this is a compiler bug.
  2. Avoid the combination that triggers it (e.g. `for<'a>` binder on an `async`/`gen` closure) until the nightly is fixed.
  3. Bisect nightlies to find when the regression entered and pin to the last working nightly.
  4. If hacking on rustc: ensure the dispatch in `lower_expr_closure` routes any `CoroutineKind::Desugared` to `lower_expr_coroutine_closure` before reaching `closure_movability_for_fn`.

Example fix

// before — explicit binder on an async closure that bypasses coroutine-closure lowering
let f = for<'a> async |x: &'a u8| { x };

// after — drop the explicit binder or use a plain async closure
let f = async |x: &u8| { x };
Defensive patterns

Strategy: fallback

Validate before calling

// A non-async/gen closure became async/gen during lowering — a compiler
// invariant violation, usually triggered by macros that synthesize closures.
// No pre-API check exists; the defense is to WRITE qualifiers literally.
//
// Good (literal):  let f = async move || { .. };
// Risky (generated): proc-macro emits `|| { .. }` then rewrites it async.
//
// If a macro builds closures, assert the async-ness flag before emitting:
//   assert_eq!(generated_closure_is_async, intended_async);

Prevention

When it happens

Trigger: Triggered when `lower_expr_closure` (or equivalent) calls `closure_movability_for_fn` with a `coroutine_kind` that is `CoroutineKind::Desugared(Gen|Async|AsyncGen, _)` — i.e. the closure had an `async move` / `gen move` body but was not redirected to `lower_expr_coroutine_closure` at the earlier dispatch site. The offending `match` arm at closure.rs:265-271 catches exactly the three Desugared variants.

Common situations: Surfaces during rustc development when refactoring closure/coroutine dispatch (e.g. changing which closures go through `lower_expr_coroutine_closure`), or in nightlies with regressions around `async`/`gen` closures combined with `move` or explicit binders (`for<'a> async |x| ...`). The `gen`/`async gen` closures feature is nightly-only and under active work.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/8422fad60f379f3a.json. Report an issue: GitHub.