Conversation
Adding costs could overflow the cost type. Debug builds panicked; release builds wrapped and returned a small, plausible-looking, wrong cost. The cost type is only required to be Zero + Ord + Copy, so checked_add is not available and requiring CheckedAdd would be a breaking change. Costs are required to be non-negative, though, and that is enough to spot a wrapped sum without a new bound: adding a non-negative value can never produce a smaller one, so a sum that compares less than the value it was added to must have wrapped. Release builds now agree with debug builds. Cost types whose addition saturates, and floating point costs which reach infinity rather than wrapping, compare greater and are left alone, so the documented workaround of wrapping the cost type keeps working. Also documents the panic on the affected functions, and drops five expect(clippy::missing_panics_doc) attributes that those docs make unnecessary.
tachsin
force-pushed
the
fix/cost-overflow
branch
from
September 12, 2026 07:17
77b39d1 to
aa479f4
Compare
This was referenced Sep 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #598.
Adding costs can overflow the cost type. Debug builds panic; release builds wrap and return a small, plausible-looking, wrong cost. Three edges of
u32::MAX / 2currently give 2,147,483,645, which is6442450941 mod 2^32, and nothing in the result says so.Approach
The blocker recorded in the issue was that checked arithmetic would need
num_traits::CheckedAddorSaturatingAddonC, which would be breaking. It turns out the existing bounds are enough:Cis alreadyZero + Ord + Copy,num_traits::ZeroimpliesAdd<Output = Self>, and costs are required to be non-negative. Adding a non-negative value can never produce a smaller one, so a wrapped sum is detectable by comparison:No new trait bound, so this can ship in a patch release. Release builds now agree with debug builds rather than returning a wrapped cost.
The panic lives in a separate
#[cold] #[inline(never)]function so thatadd_costsstays small enough to inline.What is left alone
Covered
astar,astar_bag,astar_bag_collect,dijkstra,dijkstra_bidirectional,dijkstra_all,dijkstra_partial,dijkstra_reach,fringe,idastar,yen. Each gains the# Panicssection requested in the issue.Adding those sections made five
#[expect(clippy::missing_panics_doc)]attributes unfulfilled, so they are removed here. Those functions could always panic; the lint had been silenced rather than satisfied. Worth noting that clippy does not catch this class on its own, sincemissing_panics_docdoes not trace through a call into a private helper.Tests
tests/cost_overflow.rscovers every affected entry point, the heuristic addition specifically (the case in the issue), the saturating-cost-type workaround, and that ordinary costs are unchanged.Against current
mainthey fail in release, which is where the bug is: 11 of 13 fail, the two that pass being the two meant to behave identically either way. In debug they pass againstmaintoo, because debug builds already panicked there, which is the behaviour this PR extends to release. Theshould_panicexpectations therefore match onoverflowrather than the exact message, since debug trips the built-inattempt to add with overflowinside+before the library's own check is reached.Cost
This puts a branch in the innermost loop, so it is not free, but it is cheap.
Instruction counts, comparing
mainagainst this branch. These use the corrected benchmarks from #835, because the ones currently onmaindo not measure the work they run (#833) and gave a badly misleading answer here; see the note below.corner_to_corner_idastarno_path_fringeno_path_dijkstracorner_to_corner_dijkstracorner_to_corner_astarcorner_to_corner_fringeno_path_astarcorner_to_corner_bfsno_path_bfscorner_to_corner_bfs_bidirectionalno_path_bfs_bidirectionalcorner_to_corner_iddfsEvery algorithm that adds costs pays between 0.6% and 3.3%. Five of the six that do not add costs are unchanged to the instruction. The sixth,
dfs, moves -6.7% despite not being touched, which is codegen drift and the one blemish on an otherwise very clean set.Wall clock shows no measurable change: on a 256x256 grid, fastest of 40 runs over 5 alternated rounds,
dijkstracame out 5.4% faster with the check whilebfs, which uses no costs and cannot be affected, came out 5.8% faster in the same run, so that is layout rather than the check.A correction
An earlier version of this description reported +18.6% on
corner_to_corner_dijkstra. That figure was an artefact of #833. With the benchmarks as they stand onmain,dijkstrais inlined wholesale into the benchmark harness, and adding a branch to its inner loop upends that inlining, so the comparison measured the harness rather than the change. With the search behind a call the optimiser cannot see through, which is also closer to how callers actually invoke it, the cost is 0.7%. I am sorry for the noise; the number stood for a few hours.Measurements on Windows 11, Intel Core Ultra 7 265K, 64 GB RAM; instruction counts from WSL2 Ubuntu 24.04 on the same machine with valgrind 3.22.0 and iai-callgrind 0.16.1.