iced-rs/iced · error

Downcast on stateless state

Error message

Downcast on stateless state

What it means

`State::downcast_ref<T>` panics when the widget's `State` is `State::None`, i.e. the widget holds no state at all. The library treats downcasting a stateless state as a programming error rather than a recoverable condition, because the caller has already assumed a concrete state type exists. In iced, `State::None` is used for widgets with no state or as a placeholder after state removal.

Source

Thrown at core/src/widget/tree.rs:255

impl State {
    /// Creates a new [`State`].
    pub fn new<T>(state: T) -> Self
    where
        T: 'static,
    {
        State::Some(Box::new(state))
    }

    /// Downcasts the [`State`] to `T` and returns a reference to it.
    ///
    /// # Panics
    /// This method will panic if the downcast fails or the [`State`] is [`State::None`].
    pub fn downcast_ref<T>(&self) -> &T
    where
        T: 'static,
    {
        match self {
            State::None => panic!("Downcast on stateless state"),
            State::Some(state) => state.downcast_ref().expect("Downcast widget state"),
        }
    }

    /// Downcasts the [`State`] to `T` and returns a mutable reference to it.
    ///
    /// # Panics
    /// This method will panic if the downcast fails or the [`State`] is [`State::None`].
    pub fn downcast_mut<T>(&mut self) -> &mut T
    where
        T: 'static,
    {
        match self {
            State::None => panic!("Downcast on stateless state"),
            State::Some(state) => state.downcast_mut().expect("Downcast widget state"),
        }
    }
}

View on GitHub (pinned to d146509d89)

Solutions

  1. Check the state kind before downcasting: match on the returned `State` and only call `downcast_ref` for `State::Some`.
  2. Ensure state is initialized (e.g. via `State::new(...)` in the widget's `update`/`Widget::state`) before any downcast.
  3. Verify the widget id is valid and the widget still exists in the tree at the time of access.
  4. If a downcast can legitimately fail, use the internal `state.downcast_ref()` on the `State::Some` payload (returns `Option`/`Result`) instead of panicking.

Example fix

// before
let s = tree.get_state(id).downcast_ref::<MyState>();
// after
let s = match tree.get_state(id) {
    widget::tree::State::Some(_) => tree.get_state(id).downcast_ref::<MyState>(),
    widget::tree::State::None => return, // state not initialized yet
};
Defensive patterns

Strategy: type-guard

Validate before calling

let state = tree.get_state(id);
let is_ready = matches!(state, widget::tree::State::Some(_));
if !is_ready { return; }

Type guard

fn has_state(state: &widget::tree::State) -> bool {
    matches!(state, widget::tree::State::Some(_))
}

Try / catch

// Rust panics are not catchable by Result; use std::panic::catch_unwind only at boundaries
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    tree.get_state(id).downcast_ref::<MyState>()
}));

Prevention

When it happens

Trigger: Calling `tree.get_state(widget_id).downcast_ref::<MyState>()` on a widget whose state was never created, was reset to `State::None` (e.g. via `tree.set_state(State::None)` or widget diffing/removal), or whose id points at a stale/removed widget entry.

Common situations: Accessing widget state before the first `view` pass populated it; ids invalidated after widget tree rebuilds; custom widgets that lazily create state but are queried earlier; reusing a widget id across two tree positions.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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