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
- Defer the delete until the drag finishes (end drag handler performs the delete).
- Cancel/end the drag state before calling maybe_delete.
- 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
- Queue deletions triggered by keyboard/timers until mouse-up
- Never mutate the World's object set from inside drag handlers
- Document that hovered+dragging objects must not be deleted
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
- Can't delete ; it's not in the World
- Failed to load svg from bytes. cache_key
- This build of A/B Street stores player data in…
- Can't find the data/ directory
- CityName::new( , ) has a country code that isn't two letters
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)