linebender/druid · error

Application state already borrowed

Error message

Application state already borrowed

What it means

`Application::run` stores its state in a `RefCell`. If `try_borrow_mut` fails, the state is already mutably borrowed (i.e. `run` is being re-entered or the state is borrowed elsewhere), so it panics to avoid aliased mutation of the application state.

Solutions

  1. Never call `Application::run` from inside an app handler or callback; let the outer `run` continue
  2. Ensure no code path borrows the application state while `run` executes
  3. Keep `run` on the main entry path and drive logic via the event loop/handler instead

Example fix

// before
fn on_command(&mut self, ...) {
    self.app.run(None); // re-entrant borrow -> panic
}

// after
fn on_command(&mut self, ...) {
    // handle logic via events; the outer run() loop keeps running
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Only call run from the top-level main function, never from handlers/callbacks

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| app.run(handler)));
// Prefer prevention: do not call run re-entrantly.

Prevention

When it happens

Trigger: Re-entrant call to `run` (directly or via a handler while the first `run` is still on the stack), or some other code holding a mutable borrow of the application's internal state cell.

Common situations: Calling `run` from within an event handler or a callback triggered by the running loop; accidentally re-entering `run` through recursion.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/0281c97d2904c336. Report an issue: GitHub.

Appendix: source

Thrown at druid-shell/src/application.rs:135

    /// Start the `Application` runloop.
    ///
    /// The provided `handler` will be used to inform of events.
    ///
    /// This will consume the `Application` and block the current thread
    /// until the `Application` has finished executing.
    ///
    /// # Panics
    ///
    /// Panics if the `Application` is already running.
    pub fn run(self, handler: Option<Box<dyn AppHandler>>) {
        // Make sure this application hasn't run() yet.
        if let Ok(mut state) = self.state.try_borrow_mut() {
            if state.running {
                panic!("Application is already running");
            }
            state.running = true;
        } else {
            panic!("Application state already borrowed");
        }

        // Run the platform application
        self.backend_app.run(handler);

        // This application is no longer active, so clear the global reference
        GLOBAL_APP.with(|global_app| {
            *global_app.borrow_mut() = None;
        });
        // .. and release the main thread
        util::release_main_thread();
        // .. and mark as done so a new sequence can start
        APPLICATION_CREATED
            .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)
            .expect("Application marked as not created while still running.");
    }

    /// Quit the `Application`.

View on GitHub (pinned to 0f8b1195e4)