Uncovering 140 Bugs After Porting a Python Library to Rust
A developer shares insights from porting a string similarity Python library to Rust, encountering 140 divergences and architectural hurdles.

Stock photo for illustration only, not from the actual event
- Porting code requires proving identical behavior, not just getting code to compile successfully.
- Using a JSON-based Subprocess CLI creates strict serialization limits for functions.
- Differential fuzzing successfully caught 140 divergences missed by standard unit tests.
- Jaro-Winkler bugs stemmed from overlooking a 35-year-old academic threshold of 0.7.
Porting a library from one programming language to another goes far beyond simply translating code so that it compiles. The actual engineering hurdle is proving that the newly written code produces the exact same outcomes as the legacy codebase across over thirty algorithms. This narrative details porting a string similarity library from Python to Rust under the name textdistance-rs.
The original Python library encompasses numerous edit-based distances like Levenshtein and Hamming, token-based measures including Jaccard and cosine, sequence-based methods, phonetic algorithms, and compression-based distances totaling more than thirty algorithms reimplemented in Rust. Before writing any code, the developer had to establish how an existing suite of 397 Python tests would interact with the Rust implementation.
- Chose a standalone binary subprocess CLI communicating via JSON over standard input and output.
- Implemented a thin Python adapter to shell out to the Rust core binary.
- Opted for this design for speed while inadvertently locking future architectural paths.

Stock photo for illustration only, not from the actual event
Opting for a subprocess architecture traded development velocity and process isolation for serialization flexibility. This design choice inherently prevented complex structures like live Python interpreter closures or lambdas from crossing the process boundary, highlighting how early architectural compromises directly dictate testing limitations down the road.
The most grueling debugging phase traced directly back to the Jaro-Winkler algorithm, which relies on a 1990 research paper by William Winkler. The formula introduces a prefix bonus intended exclusively for strings sharing initial characters. The initial Rust implementation erroneously applied this bonus unconditionally, failing to check Winkler's rule that the base Jaro score must exceed 0.7 before the bonus applies.
"When you port a library, you inherit its citations, not just its code."
Project AuthorThis raises the question of how 140 divergences were discovered despite unit tests passing cleanly. The solution utilized differential fuzzing by executing both implementations across randomized adversarial inputs generated by the Hypothesis testing library. This technique compared outputs directly without needing prior knowledge of the correct answer, shrinking complex failures into minimal reproducible counterexamples.
140Divergences Discovered397Original Python TestsNevertheless, alignment algorithms such as Needleman-Wunsch and Smith-Waterman accept arbitrary Python callables like lambdas to score character similarity. Because these closures cannot be serialized into JSON across the subprocess pipe, 22 specific tests fail permanently due to architectural constraints—representing the foreseeable cost of the initial design decision.
Source:
Dev.to
Found something wrong in this article? Report an issue with this article
Comments
Leave a Comment