slint-ui/slint · critical
Trying to upgrade a Weak from a different thread than the…
Error message
Trying to upgrade a Weak from a different thread than the one it belongs to
What it means
Weak component handles in Slint are thread-affine: `Weak::unwrap()` records the thread of the creating component and panics if `unwrap()` is called from a different thread (with the std feature). This guards against upgrading a component handle from a non-UI thread, which is unsupported.
Solutions
- Perform UI access on the thread that created the component (the main/UI thread); use slint::invoke_from_event_loop or a `slint::VecModel` wrapped and updated via the event loop instead
- Move raw data (not component handles) into the worker thread, then post results back with `slint::invoke_from_event_loop(move || { /* update UI */ })`
- If spawning your own UI thread, create the component on that thread and only unwrap there
Example fix
// before
let weak = ui.as_weak();
std::thread::spawn(move || {
let ui = weak.unwrap(); // panics: wrong thread
ui.set_result(...);
});
// after
let weak = ui.as_weak();
std::thread::spawn(move || {
let result = compute();
slint::invoke_from_event_loop(move || {
weak.unwrap().set_result(result);
}).unwrap();
}); Defensive patterns
Strategy: type-guard
Type guard
// Rust
fn same_thread(this_thread: std::thread::ThreadId) -> bool {
std::thread::current().id() == this_thread
} Try / catch
// prefer not to panic at all:
// route UI access through the event loop
slint::invoke_from_event_loop(move || {
if let Some(ui) = weak.upgrade() { ui.set_result(v); }
}).unwrap(); Prevention
- Never move ComponentHandle/Weak across threads; move plain data instead
- Always update UI state via slint::invoke_from_event_loop from background threads
- In async code, schedule UI mutations on the main/UI thread executor
When it happens
Trigger: Calling `weak.unwrap()` (or `upgrade()`) on a `Weak<T>`/`ComponentHandle` clone captured and moved into a non-UI thread, e.g. inside a `std::thread::spawn` closure or a background worker callback, when that thread differs from the one that created the component.
Common situations: Spawning a worker thread that tries to touch UI state; async runtimes (tokio) executing a closure on a thread pool; queueing a component handle for use in a backend thread.
Related errors
- Component should cleanly upgrade here
- a setter belongs to a field
- an identifier
- attempt to multiply with overflow
- binding was of the wrong type
AI-assisted analysis of slint-ui/slint@bb937076de (2026-09-16).
Data as JSON: /api/errors/4f650fa72ab5b8ff.
Report an issue: GitHub.
Appendix: source
Thrown at internal/core/api.rs:1187
/// This also returns None if the current thread is not the thread that created
/// the component
pub fn upgrade(&self) -> Option<T> {
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
return None;
}
T::upgrade_from_weak_inner(&self.inner)
}
/// Convenience function that returns a new strongly referenced component if
/// some other instance still holds a strong reference and the current thread
/// is the thread that created this component.
/// Otherwise, this function panics.
#[track_caller]
pub fn unwrap(&self) -> T {
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
panic!(
"Trying to upgrade a Weak from a different thread than the one it belongs to"
);
}
T::upgrade_from_weak_inner(&self.inner)
.expect("The Weak doesn't hold a valid component")
}
/// A helper function to allow creation on `component_factory::Component` from
/// a `ComponentHandle`
pub(crate) fn inner(&self) -> T::WeakInner {
self.inner.clone()
}
/// Convenience function that combines [`invoke_from_event_loop()`] with [`Self::upgrade()`]
///
/// The given functor will be added to an internal queue and will wake the event loop.
/// On the next iteration of the event loop, the functor will be executed with a `T` as an argument.
///View on GitHub (pinned to bb937076de)