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
- Only pass PathStep::Turn as the replacement when modifying a step inside an uber_turn
- Check whether the old step is part of an uber_turn and rebuild the whole path instead of patching the step
- Extend modify_step to remove/split the uber_turn when replacing with a non-turn step (library change)
- 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
- Check uber_turn membership of the old step before replacing
- Only substitute Turn-for-Turn inside uber_turns
- Prefer full path rebuilds over step patching near complex intersections
- Add a unit test replacing each PathStep variant
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
- PathConstraints::from_lt
- Negative dist_ahead?!
- modify_step broke total_length, it's now
- Empty path
- pathfind() returned path that warps
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)