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
- Make `T` exactly the type the widget returns from `Widget::state()`
- Guard before downcasting: check the state is `State::Some` and `(*any).is::<T>()` before calling downcast_ref
- Give list items stable keys so reordering cannot misalign state nodes
- 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
- Only downcast state your own widget created via Widget::state()
- Give dynamic children stable keys so tree nodes cannot misalign after reordering
- When changing a widget's state type, treat it as a breaking change for tree reuse
- Log the expected vs actual TypeId in your guard to make mismatches debuggable
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
- not implemented
- Encode input message
- Encode input message
- lock font name cache
- Write application metadata
AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16).
Data as JSON: /api/errors/926f0f2dece81260.
Report an issue: GitHub.