Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-435 · Case file with fixtures · Case 407 of 694 · Compiler evidence

A Nested Rust Function Cannot Capture an Outer Generic Parameter

A nested fn is an item with its own generic scope, not a closure. Give it independent generic parameters, move it to an impl helper, or use a closure when capturing the outer type and local environment is intended.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
Lexical nesting limits visibility but a nested fn remains an independent item, so unlike a closure it captures neither local values nor the enclosing generic environment.
First discriminating check
Give the inner item its own generic parameters, move reusable logic to an associated helper, or use a closure when capture is intended.

I placed a small helper function inside a generic function and used the outer T in its parameter. Rust reported E0401: it cannot use generic parameters from the outer item.

The failing fixture declares outer<T> and then a nested fn inner(item: T). Its visual indentation suggests nesting, but its type scope behaves like an independent item.

A nested fn is still an item

Rust permits item declarations inside blocks. Their visibility is local to that block, but they do not capture the surrounding function's generic parameters or local variables.

The E0401 explanation says inner items are basically like top-level items except for where they can be used. That sentence resolves much of the confusion.

If the compiler generated one inner for every instantiation of outer<T>, it would be behaving like a capturing construct. Rust instead requires the inner item to declare its own complete signature.

Give the helper its own generic parameter

The repaired fixture writes:

fn inner<U>(item: U) {
    drop(item);
}

outer passes its T value, and inference chooses U = T for that call. The two parameter names describe different declarations even when one use unifies their concrete types.

Any bounds required by inner must be repeated on U. Bounds available on outer T do not flow into a new generic item automatically.

Use a closure when capture is intended

A closure is an expression whose generated type can capture values and borrow or own parts of the surrounding environment:

let inner = |item: T| drop(item);
inner(value);

The closure's annotation can refer to outer T, and its body can use local variables according to closure capture rules.

This is the better form for a one-off helper tied to local state. A nested function is better when capture is unwanted and an item-like reusable helper is clearer.

The closure Reference explains that closures have unique types and capture modes derived from use.

Move reusable logic to an impl or module function

If the helper belongs to a generic type, an associated private method often communicates ownership better:

impl<T> Wrapper<T> {
    fn inner(item: T) { /* ... */ }
}

Now T belongs to the impl's generic scope and methods can use it. A module-level fn inner<U> is also easy to test and reuse.

I avoid deep local function trees because they hide dependencies without gaining closure capture.

Local type aliases and structs follow the same rule

E0401 can appear for a local type alias using outer T, or a structure declared inside a generic function with a T field. These are items too.

They need their own generic parameters, or the design should use an expression-level value. Moving the declaration does not change the underlying rule unless it moves into a generic impl or item that declares the parameter.

Const items do not specialise per generic call

A local const is an item and cannot depend on an outer generic parameter as if it were recreated for each monomorphisation. Associated constants, const expressions in supported generic contexts, or ordinary local let bindings can model different needs.

I ask whether I need compile-time identity, a runtime computed value, or only a named expression before choosing among them.

This separation helps auditing

An inner function that cannot capture local state has explicit dependencies. It cannot quietly borrow a lock guard, request object, or mutable buffer from the outer body. That makes refactoring and testing predictable.

A closure intentionally trades this independence for access to the environment. Its Fn, FnMut, or FnOnce behaviour then reflects how it captures and uses values.

My E0401 checklist

  • Is the nested declaration an item or an expression?
  • Should it capture local variables or outer generic types?
  • Can it declare an independent generic parameter and bounds?
  • Would a closure better express local capture?
  • Does the helper belong as an associated method?
  • Is a local const or type alias mistakenly expected to specialise?
  • Are repeated bounds still correct for the helper itself?

The core principle is that lexical nesting controls where an item is visible, not which generic environment it captures. Nested functions remain independent items. I make their parameters explicit or choose a closure when dependency on the surrounding invocation is the actual design.