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
- Report to the rustc ICE tracker with a reproducer using `#![feature(gen_blocks)]` / `async` closures — this is a compiler bug.
- Avoid the combination that triggers it (e.g. `for<'a>` binder on an `async`/`gen` closure) until the nightly is fixed.
- Bisect nightlies to find when the regression entered and pin to the last working nightly.
- 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
- Write `async`/`gen` closure qualifiers literally in source; do not generate them via proc-macros
- If a macro synthesizes closures, set the coroutine kind explicitly before emitting the node
- Avoid macros that post-process a closure to flip its async-ness
- Bisect the macro expansion that triggers it and file an ICE with the reduced output
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
- assignment does not match variant
- use of `await` outside of an async context.
- shouldn't exist here
- must contain self type as `SelfTy` propagation kind is speci
- arg must exist for infer
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/8422fad60f379f3a.json.
Report an issue: GitHub.