a-b-street/abstreet · error

doesn't contain

Error message

{} doesn't contain {}

What it means

Road::dir_and_offset scans both directions' child lane lists for the given LaneID to return its direction and index. Panics when the road contains no such lane, i.e. the lane belongs to a different road.

Solutions

  1. Verify lane belongs to the road: road.children(Direction::Fwd).iter().chain(children(Back)) contains the ID before calling.
  2. Recompute the LaneID from the same road reference rather than caching it.
  3. Check that edits/persisted IDs match the current map version; regenerate or re-target by orig_id.

Example fix

// before
let (dir, off) = road.dir_and_offset(lane);
// after
if !road.children(Direction::Fwd).iter().chain(road.children(Direction::Back).iter()).any(|(l, _)| *l == lane) {
    anyhow::bail!("{} not in {}", lane, road.id);
}
let (dir, off) = road.dir_and_offset(lane);
Defensive patterns

Strategy: type-guard

Type guard

fn dir_and_offset_safe(road: &Road, lane: LaneID) -> Option<(Direction, usize)> {
    for dir in [Direction::Fwd, Direction::Back] {
        if let Some(idx) = road.children(dir).iter().position(|p| p.0 == lane) {
            return Some((dir, idx));
        }
    }
    None
}

Prevention

When it happens

Trigger: Calling road.dir_and_offset(lane) with a LaneID that is not a child of that road — e.g. derived from a different road, or from a stale/invalid ID after edits or re-import.

Common situations: Lane-offset math in edits and drawing code; mixing up lane IDs between two adjacent roads; loading edits JSON against a regenerated map where IDs shifted.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/b8db3ac41c86254e. Report an issue: GitHub.

Appendix: source

Thrown at map_model/src/objects/road.rs:239

                allowed_turns: Default::default(),
            })
            .collect()
    }

    pub fn shift_from_left_side(&self, width_from_left_side: Distance) -> Result<PolyLine> {
        self.center_pts
            .shift_from_center(self.get_width(), width_from_left_side)
    }

    /// lane must belong to this road. Offset 0 is the centermost lane on each side of a road, then
    /// it counts up from there. Note this is a different offset than `offset`!
    pub(crate) fn dir_and_offset(&self, lane: LaneID) -> (Direction, usize) {
        for dir in [Direction::Fwd, Direction::Back] {
            if let Some(idx) = self.children(dir).iter().position(|pair| pair.0 == lane) {
                return (dir, idx);
            }
        }
        panic!("{} doesn't contain {}", self.id, lane);
    }

    pub fn parking_to_driving(&self, parking: LaneID) -> Option<LaneID> {
        self.find_closest_lane(parking, |l| l.is_driving())
    }

    pub(crate) fn speed_limit_from_osm(&self) -> Speed {
        if let Some(limit) = self.osm_tags.get("maxspeed") {
            if let Some(speed) = if let Ok(kmph) = limit.parse::<f64>() {
                Some(Speed::km_per_hour(kmph))
            } else if let Some(mph) = limit
                .strip_suffix(" mph")
                .and_then(|x| x.parse::<f64>().ok())
            {
                Some(Speed::miles_per_hour(mph))
            } else {
                None
            } {

View on GitHub (pinned to 0964f29315)