iced-rs/iced · error

Downcast widget state

Error message

Downcast widget state

What it means

`widget::Tree` stores each widget's state as a type-erased `Box<dyn Any>`; `State::downcast_ref::<T>()` panics with "Downcast widget state" when the stored concrete type is not `T` (a separate message, "Downcast on stateless state", covers `State::None`). It is an invariant check that the widget asking for state is the widget that created it.

Source

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

    /// 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 2cffa99b39)

Solutions

  1. Make `T` exactly the type the widget returns from `Widget::state()`
  2. Guard before downcasting: check the state is `State::Some` and `(*any).is::<T>()` before calling downcast_ref
  3. Give list items stable keys so reordering cannot misalign state nodes
  4. Return a dedicated state type from your widget and never downcast state you did not create

Example fix

// before
let state = tree.state.downcast_ref::<MyState>(); // panics on mismatch
// after
use iced_core::widget::tree::State;
match &tree.state {
    State::Some(any) if (*any).is::<MyState>() => {
        let state = tree.state.downcast_ref::<MyState>();
        // ...
    }
    _ => { /* not our state: skip or log */ }
}
Defensive patterns

Strategy: type-guard

Validate before calling

use iced_core::widget::tree::State;

fn can_downcast<T: 'static>(tree: &iced_core::widget::Tree) -> bool {
    match &tree.state {
        State::None => false,
        State::Some(any) => any.is::<T>(),
    }
}

// usage
if can_downcast::<MyState>(tree) {
    let state = tree.state.downcast_ref::<MyState>();
}

Type guard

fn tree_state_is<T: 'static>(tree: &iced_core::widget::Tree) -> bool {
    matches!(&tree.state, iced_core::widget::tree::State::Some(any) if any.is::<T>())
}

Prevention

When it happens

Trigger: Calling `tree.state.downcast_ref::<T>()` where the Tree node was built by a widget whose `state()` returned a different type — custom widgets whose view/operate/diff code assumes another widget's state, or a reused tree whose nodes line up with different widgets after a structural change.

Common situations: Implementing a custom Widget/Operate with the wrong state type parameter; changing a widget's state struct between versions while tree diffing reuses nodes; reordering unkeyed list children so state nodes misalign; copy-pasting a custom widget without updating its state type.

Related errors


AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16). Data as JSON: /api/errors/926f0f2dece81260. Report an issue: GitHub.