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
- Move captured data into the subscription itself using `Subscription::with` (or the recipe/`batch` APIs) instead of the closure.
- Make the closure truly non-capturing: pass needed data through the subscription message payload and remap in `update`.
- Recreate the needed value inside the closure (e.g. recompute a constant) so nothing is captured.
- 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
- Never reference outer variables inside `Subscription::map`/`filter_map` closures.
- Use `Subscription::with` whenever data must flow into a subscription.
- Keep subscription mapping functions as free functions or fn items where possible.
- Run subscription-heavy screens in debug builds to catch the const panic early.
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
- Write font system
- Line layout should be cached
- Editor should always be initialized
- Editor cannot have multiple strong references
- Write font system
AI-assisted analysis of iced-rs/iced@d146509d89 (2026-09-11).
Data as JSON: /api/errors/ffd0508542be0ee7.
Report an issue: GitHub.