a-b-street/abstreet · error

Can't delete mid-drag

Error message

Can't delete {:?} mid-drag

What it means

World::maybe_delete panics if asked to delete the object currently being dragged. Deleting mid-drag would leave the drag state (dragging_from) pointing at a removed object, breaking internal invariants, so even the tolerant delete refuses in that case.

Solutions

  1. Defer the delete until the drag finishes (end drag handler performs the delete).
  2. Cancel/end the drag state before calling maybe_delete.
  3. Check world's dragging state (if exposed) before deleting a hovered object.

Example fix

// before
world.maybe_delete(hovered_id);
// after
if !world.is_dragging() { world.maybe_delete(hovered_id); } else { pending_deletes.push(hovered_id); }
Defensive patterns

Strategy: validation

Validate before calling

// Defer deletes during active drags
if drag_in_progress && hovered == Some(id) { defer_delete(id); } else { world.maybe_delete(id); }

Prevention

When it happens

Trigger: Calling maybe_delete(id) while that same ID is hovered AND a drag is in progress (dragging_from is Some) — e.g. deleting an object from a keyboard/timer event during an active mouse drag.

Common situations: Key handlers or async events removing objects while the user still holds the mouse button on them; test harnesses simulating drag+delete concurrently.

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 a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/e8872361c37e46b7. Report an issue: GitHub.

Appendix: source

Thrown at widgetry/src/mapspace/world.rs:422

    pub fn delete_before_replacement(&mut self, id: ID) {
        if self.objects.remove(&id).is_some() {
            if self.quadtree.remove(id).is_none() {
                // This can happen for objects that're out-of-bounds. One example is intersections
                // in map_editor.
                warn!("{:?} wasn't in the quadtree", id);
            }
        } else {
            panic!("Can't delete {:?}; it's not in the World", id);
        }
    }

    /// Like delete, but doesn't crash if the object doesn't exist
    pub fn maybe_delete(&mut self, id: ID) {
        if self.hovering == Some(id) {
            self.hovering = None;
            self.draw_hovering = None;
            if self.dragging_from.is_some() {
                panic!("Can't delete {:?} mid-drag", id);
            }
        }

        if self.objects.remove(&id).is_some() {
            if self.quadtree.remove(id).is_none() {
                // This can happen for objects that're out-of-bounds. One example is intersections
                // in map_editor.
                warn!("{:?} wasn't in the quadtree", id);
            }
        }
    }

    /// After adding all objects to a `World`, call this to initially detect if the cursor is
    /// hovering on an object. This may also be called after adding or deleting objects to
    /// immediately recalculate hover before the mouse moves.
    // TODO Maybe we should automatically do this after mutations? Except we don't want to in the
    // middle of a bulk operation, like initial setup or a many-step mutation. So maybe the caller
    // really should handle it.

View on GitHub (pinned to 0964f29315)