linebender/druid · critical

Expected selector " " but the command was " ".

Error message

Expected selector "{}" but the command was "{}".

What it means

Druid's Command::get_unchecked<T> panics when the command being handled does not carry the requested Selector<T> — the command's symbol differs from the selector's symbol, so get(selector) returned None. Because get_unchecked skips the Option check, the library cannot recover and aborts. This typically means the widget thought it was receiving one command but got another, or two Selector keys with the same string but different types collide.

Solutions

  1. Guard with command.is(selector) before calling get_unchecked, and handle or ignore non-matching commands instead of unwrapping.
  2. Use the safe Command::get(selector) which returns Option<&T>, and match on None gracefully.
  3. Audit all Selector::new key strings for duplicates; give every selector a unique key (conventionally prefixed with the module/feature name).
  4. Ensure the command's Target matches: commands sent to a sub-window or specific widget id may be consumed elsewhere; verify the widget actually is the intended recipient.
  5. If you own both sides, change get_unchecked to get and log the unexpected selector rather than panicking in release builds.

Example fix

// before
selector!(OPEN_FILE: Arc<PathBuf>);
fn command(&mut self, ctx, cmd) {
    let path = cmd.get_unchecked(OPEN_FILE);
    ...
}
// after
fn command(&mut self, ctx, cmd) {
    if let Some(path) = cmd.get(OPEN_FILE) {
        ...
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before unwrapping in a widget handler:
if !cmd.is(MY_SELECTOR) {
    return; // not our command; let others handle it
}

Type guard

fn is_my_selector<T>(cmd: &Command, sel: Selector<T>) -> bool {
    cmd.is(sel)
}

Prevention

When it happens

Trigger: Calling command.get_unchecked(MY_SELECTOR) inside a widget's command() handler when the notification order delivers a different command (e.g. another widget's command with a colliding or forwarded selector). Defining two Selector::new("save") constants with different type parameters, so the wrong one matches at runtime. Handling a command in the wrong scope: a sub-window (via new_sub_window) or an edited target returns a command whose selector the parent widget then unwraps unchecked. A typo or copy-pasted Selector key string reused across unrelated features.

Common situations: Copy-pasting a Selector declaration and forgetting to change the key string, so two features share one key with different payloads. A widget that registers for multiple commands but uses get_unchecked for all of them instead of is() guards. Window/controller code (new_window, show_open_panel, show_save_panel, invalidate_ime) assuming the returned command is always the one they just sent when another handler intercepted it.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at druid/src/command.rs:503

    }

    /// Returns a reference to this `Command`'s payload.
    ///
    /// If the selector has already been checked with [`is`], then `get_unchecked` can be used safely.
    /// Otherwise you should use [`get`] instead.
    ///
    /// # Panics
    ///
    /// Panics when `self.is(selector) == false`.
    ///
    /// Panics when the payload has a different type, than what the selector is supposed to carry.
    /// This can happen when two selectors with different types but the same key are used.
    ///
    /// [`is`]: #method.is
    /// [`get`]: #method.get
    pub fn get_unchecked<T: Any>(&self, selector: Selector<T>) -> &T {
        self.get(selector).unwrap_or_else(|| {
            panic!(
                "Expected selector \"{}\" but the command was \"{}\".",
                selector.symbol(),
                self.symbol
            )
        })
    }
}

impl Notification {
    /// Returns `true` if `self` matches this [`Selector`].
    pub fn is<T>(&self, selector: Selector<T>) -> bool {
        self.symbol == selector.symbol()
    }

    /// Returns the payload for this [`Selector`], if the selector matches.
    ///
    /// # Panics
    ///

View on GitHub (pinned to 0f8b1195e4)