a-b-street/abstreet · error

expected turn, but found

Error message

expected turn, but found {:?}

What it means

Path::modify_step replaces a step in the path; when the replaced step is part of an uber_turn, the corresponding uber_turn path entry must also be replaced, which only works if the new step is also a Turn. If the replacement step is not a Turn, the library panics because an uber_turn would be corrupted by inserting a non-turn step into it.

Solutions

  1. Only pass PathStep::Turn as the replacement when modifying a step inside an uber_turn
  2. Check whether the old step is part of an uber_turn and rebuild the whole path instead of patching the step
  3. Extend modify_step to remove/split the uber_turn when replacing with a non-turn step (library change)
  4. Verify the replacement step's type against PathStep::Turn before calling modify_step

Example fix

// before
path.modify_step(idx, PathStep::Contraflow(drive), map);
// after
if matches!(path.steps[idx], PathStep::Turn(_)) && !path.uber_turns.iter().any(|ut| ut.path.contains(&old_turn)) {
    path.modify_step(idx, PathStep::Contraflow(drive), map);
} else {
    // rebuild the path; replacing an uber_turn member requires a Turn
}
Defensive patterns

Strategy: type-guard

Validate before calling

if let PathStep::Turn(old_turn) = path.steps[idx] {
    let in_uber_turn = path.uber_turns.iter().any(|ut| ut.path.contains(&old_turn));
    if in_uber_turn && !matches!(new_step, PathStep::Turn(_)) { /* reject or rebuild path */ }
}

Type guard

fn can_replace(old: &PathStep, new: &PathStep) -> bool {
    !matches!(old, PathStep::Turn(_)) || matches!(new, PathStep::Turn(_))
}

Try / catch

// panics are not catchable; guard the replacement type before calling:
if can_replace(&path.steps[idx], &new_step) {
    path.modify_step(idx, new_step, map);
} else {
    rebuild_path();
}

Prevention

When it happens

Trigger: Calling modify_step at an index whose current step is a PathStep::Turn that belongs to an uber_turn, while passing a replacement step that is not PathStep::Turn (e.g. a Lane or Contraflow step).

Common situations: Rerouting/editing paths for changed road geometry where a turn inside an uber_turn (complex intersection movement) is replaced with an arbitrary step; tooling that rewrites path steps without checking uber_turn membership.

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

Appendix: source

Thrown at map_model/src/pathfind/v1.rs:336

            .map(|ut| ut.path.contains(&t))
            .unwrap_or(false)
    }

    /// Trusting the caller to do this in valid ways.
    pub fn modify_step(&mut self, idx: usize, step: PathStep, map: &Map) {
        assert!(self.currently_inside_ut.is_none());
        // We're assuming this step was in the middle of the path, meaning we were planning to
        // travel its full length
        self.total_length -= self.steps[idx].as_traversable().get_polyline(map).length();

        // When replacing a turn, also update any references to it in uber_turns
        if let PathStep::Turn(old_turn) = self.steps[idx] {
            for uts in &mut self.uber_turns {
                if let Some(turn_idx) = uts.path.iter().position(|i| i == &old_turn) {
                    if let PathStep::Turn(new_turn) = step {
                        uts.path[turn_idx] = new_turn;
                    } else {
                        panic!("expected turn, but found {:?}", step);
                    }
                }
            }
        }

        self.steps[idx] = step;
        self.total_length += self.steps[idx].as_traversable().get_polyline(map).length();

        if self.total_length < Distance::ZERO {
            panic!(
                "modify_step broke total_length, it's now {}",
                self.total_length
            );
        }
    }

    pub fn current_step(&self) -> PathStep {
        self.steps[0]

View on GitHub (pinned to 0964f29315)