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

  1. 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
  2. Move raw data (not component handles) into the worker thread, then post results back with `slint::invoke_from_event_loop(move || { /* update UI */ })`
  3. 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

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


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)