iced-rs/iced · error

The Subscription closure provided is not non-capturing. Clos

Error message

The Subscription closure provided is not non-capturing. Closures given to Subscription::map or filter_map cannot capture external variables. If you need to capture state, consider using Subscription::with.

What it means

This compile-time (const-evaluated) check panics when a closure passed to `Subscription::map` or `filter_map` captures environment variables, making it non-zero-sized. Non-capturing closures are required so subscriptions can be compared for equality across `view` calls; capturing closures would make subscription identity unstable. The message points to `Subscription::with` for stateful cases.

Source

Thrown at futures/src/subscription.rs:510

    I: Hash + 'static,
    F: FnOnce(&I, EventStream) -> S,
    S: Stream<Item = T> + MaybeSend + 'static,
{
    type Output = T;

    fn hash(&self, state: &mut Hasher) {
        std::any::TypeId::of::<I>().hash(state);
        self.data.hash(state);
    }

    fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
        crate::boxed_stream((self.spawn)(&self.data, input))
    }
}

const fn check_zero_sized<T>() {
    if std::mem::size_of::<T>() != 0 {
        panic!(
            "The Subscription closure provided is not non-capturing. \
            Closures given to Subscription::map or filter_map cannot \
            capture external variables. If you need to capture state, \
            consider using Subscription::with."
        );
    }
}

View on GitHub (pinned to d146509d89)

Solutions

  1. Move captured data into the subscription itself using `Subscription::with` (or the recipe/`batch` APIs) instead of the closure.
  2. Make the closure truly non-capturing: pass needed data through the subscription message payload and remap in `update`.
  3. Recreate the needed value inside the closure (e.g. recompute a constant) so nothing is captured.
  4. Restructure with `Subscription::run` + a stateful recipe when long-lived state is required.

Example fix

// before
let id = self.id;
subscription.map(move |event| { /* uses id */ })
// after
Subscription::with(id, move |id, events| {
    events.map(move |event| { /* id available here */ })
})
Defensive patterns

Strategy: validation

Validate before calling

// Assert the closure is zero-sized (non-capturing) before use:
fn assert_non_capturing<F>() {
    assert_eq!(std::mem::size_of::<F>(), 0, "closure captures variables");
}

Type guard

fn is_non_capturing<F>(_: &F) -> bool {
    std::mem::size_of::<F>() == 0
}

Try / catch

// Panic occurs at subscription construction; wrap in catch_unwind during development
guarded_sub = std::panic::catch_unwind(|| subscription.map(no_capture_fn));

Prevention

When it happens

Trigger: `subscription.map(|m| ...)` where the closure body references a variable from the enclosing scope (e.g. an id, a channel sender, config value), or `filter_map` with a capturing predicate. The panic surfaces at runtime when the closure type is instantiated through the const fn.

Common situations: Forwarding a `ModuleId`/entity id into a mapped subscription; capturing a `Sender` to route messages; migrating code that previously built closures capturing state after upgrading to an iced version that enforces non-capturing closures.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of iced-rs/iced@d146509d89 (2026-09-11). Data as JSON: /api/errors/ffd0508542be0ee7. Report an issue: GitHub.