<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amit Kumar</title>
    <description>The latest articles on DEV Community by Amit Kumar (@amitthecoder).</description>
    <link>https://dev.to/amitthecoder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4057804%2F5c2c297d-5a08-4ca8-9fc8-4defeba748d6.jpeg</url>
      <title>DEV Community: Amit Kumar</title>
      <link>https://dev.to/amitthecoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amitthecoder"/>
    <language>en</language>
    <item>
      <title>My first blog looking to post more bout' my journey here</title>
      <dc:creator>Amit Kumar</dc:creator>
      <pubDate>Sun, 02 Aug 2026 17:42:20 +0000</pubDate>
      <link>https://dev.to/amitthecoder/my-first-blog-looking-to-post-more-bout-my-journey-here-38pe</link>
      <guid>https://dev.to/amitthecoder/my-first-blog-looking-to-post-more-bout-my-journey-here-38pe</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l" class="crayons-story__hidden-navigation-link"&gt;I ported a Python library to Rust. My fuzzer was lying to me.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/amitthecoder" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4057804%2F5c2c297d-5a08-4ca8-9fc8-4defeba748d6.jpeg" alt="amitthecoder profile" class="crayons-avatar__image" width="630" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/amitthecoder" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amit Kumar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amit Kumar
                
              
              &lt;div id="story-author-preview-content-4294253" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/amitthecoder" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4057804%2F5c2c297d-5a08-4ca8-9fc8-4defeba748d6.jpeg" class="crayons-avatar__image" alt="" width="630" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amit Kumar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 2&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l" id="article-link-4294253"&gt;
          I ported a Python library to Rust. My fuzzer was lying to me.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hackathonraptors"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hackathonraptors&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rust"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rust&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>beginners</category>
      <category>learning</category>
      <category>python</category>
      <category>rust</category>
    </item>
    <item>
      <title>I ported a Python library to Rust. My fuzzer was lying to me.</title>
      <dc:creator>Amit Kumar</dc:creator>
      <pubDate>Sun, 02 Aug 2026 17:41:34 +0000</pubDate>
      <link>https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l</link>
      <guid>https://dev.to/amitthecoder/i-ported-a-python-library-to-rust-my-fuzzer-was-lying-to-me-2k3l</guid>
      <description>&lt;p&gt;Every "port to Rust" post is the same shape: &lt;em&gt;I rewrote it in Rust and it's faster.&lt;/em&gt; This one isn't. I ported a Python string-similarity library to Rust, and the honest story is mostly about &lt;em&gt;proving&lt;/em&gt; the port, not writing it. The original's own tests pass unmodified. A differential fuzzer compares the two on hundreds of thousands of random inputs. And the hardest bug I fixed wasn't in the algorithms. It was in the tool that was supposed to be verifying them. It was telling me "0 divergences" while not actually testing most of the code.&lt;/p&gt;

&lt;p&gt;That is the part nobody writes about. Here is the whole thing: what I picked, what broke, how I proved equivalence, the bug that ate a day, and the decision I would take back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I picked
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/life4/textdistance" rel="noopener noreferrer"&gt;&lt;code&gt;textdistance&lt;/code&gt;&lt;/a&gt; is a pure-Python library with 30+ string-similarity algorithms: edit distances (Levenshtein, Damerau-Levenshtein, strcmp95), sequence metrics (LCS), token metrics (Jaccard, Sorensen), phonetic metrics, and a family of &lt;em&gt;compression-based&lt;/em&gt; metrics (NCD) that literally compress your strings and compare the lengths. That last family is why this is a good port to attempt. The compression metrics need real &lt;code&gt;liblzma&lt;/code&gt; and &lt;code&gt;libbz2&lt;/code&gt;, so I vendor them through &lt;code&gt;lzma-sys&lt;/code&gt; from a small C wrapper crate. The port is a Rust core (&lt;code&gt;tdcore&lt;/code&gt;, &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;) plus a thin PyO3 FFI (&lt;code&gt;pyapi&lt;/code&gt;) plus a Python adapter package that keeps the exact public API, wrapped for distribution with &lt;code&gt;maturin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My rule from the start: &lt;strong&gt;this is a behavioral port, not a rewrite.&lt;/strong&gt; The original's behavior &lt;em&gt;is&lt;/em&gt; the specification, including its quirks and its bugs. A port that "improves" Smith-Waterman to the textbook definition has failed. So "correct" means: same values, same types, same exceptions, same edge cases, on every input the original's own tests and a lot of random inputs can reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I proved it
&lt;/h2&gt;

&lt;p&gt;Three independent checks, and I published the scripts for all of them (&lt;code&gt;bench/&lt;/code&gt;, &lt;code&gt;scripts/&lt;/code&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The original test suite runs unmodified.&lt;/strong&gt; &lt;code&gt;tests/original/&lt;/code&gt; is pinned byte-for-byte to a specific upstream commit. All 400 tests pass. With the port's own tests that is &lt;strong&gt;428 passed, 30 deselected&lt;/strong&gt; (the 30 are &lt;code&gt;external&lt;/code&gt;; they call into third-party libs like &lt;code&gt;nltk&lt;/code&gt; and &lt;code&gt;jellyfish&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Differential fuzzing.&lt;/strong&gt; &lt;code&gt;fuzz/&lt;/code&gt; runs the &lt;em&gt;original&lt;/em&gt; package and the port side by side on identical random inputs (text, Unicode, lone surrogates, lists, varying constructor kwargs) and asserts identical outputs for &lt;code&gt;distance&lt;/code&gt;, &lt;code&gt;similarity&lt;/code&gt;, &lt;code&gt;normalized_distance&lt;/code&gt;, &lt;code&gt;normalized_similarity&lt;/code&gt;, and &lt;code&gt;maximum&lt;/code&gt;. Final runs: &lt;strong&gt;59,000 + 101,400 cases, 0 divergences&lt;/strong&gt;. Earlier I ran 1.75M + 1.37M lone-surrogate cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest-number scripts.&lt;/strong&gt; &lt;code&gt;honest_report.py&lt;/code&gt; counts &lt;code&gt;unsafe&lt;/code&gt; blocks per crate. &lt;code&gt;coverage_diff.py&lt;/code&gt; measures statement coverage of the adapter on both sides (port 87.3% vs original 79.8%; the port's real math is in Rust, so that raw percentage understates it, and I say so in the file). &lt;code&gt;cli_diff.py&lt;/code&gt; diffs the &lt;code&gt;tdc&lt;/code&gt; CLI against the original on 792 shared inputs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now the part with the meat on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: my fuzzer was lying to me
&lt;/h2&gt;

&lt;p&gt;I wrote the differential harness, let it run for hours, and saw the magic line: &lt;code&gt;divergences: 0&lt;/code&gt;. I was ready to call it a day. Then I actually read my own code.&lt;/p&gt;

&lt;p&gt;The harness was passing the same constructor kwargs to all 37 algorithms. One of the shared kwargs was &lt;code&gt;as_set=True&lt;/code&gt;. Twenty-eight of the algorithms reject &lt;code&gt;as_set&lt;/code&gt;. For those algorithms every case died of &lt;code&gt;TypeError&lt;/code&gt; at construction, the harness caught the error, and moved on. The "0 divergences" run had compared a handful of the easiest algorithms and skipped everything else. My verification tool, the thing I built so I wouldn't fool myself, was the thing fooling me.&lt;/p&gt;

&lt;p&gt;The fix was boring and correct: filter kwargs through &lt;code&gt;inspect.signature&lt;/code&gt;, identically on both the port and the reference side, so every algorithm actually gets &lt;em&gt;constructed&lt;/em&gt; and &lt;em&gt;value-compared&lt;/em&gt;. While I was in there I found two more silent problems: numpy reprs (&lt;code&gt;np.float64(3.0)&lt;/code&gt;) were being compared as strings, and &lt;code&gt;maximum&lt;/code&gt; was being compared as a bound-method repr instead of a called value. All fixed. The honest lesson is the headline: &lt;strong&gt;before you trust a differential fuzzer, test that the fuzzer is testing.&lt;/strong&gt; I would take back every minute I spent trusting the first "0 divergences."&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: the latent bug (and the one good thing the harness did)
&lt;/h2&gt;

&lt;p&gt;The repaired harness immediately found its first real divergence. It was not a port bug. It was an upstream one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;textdistance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gotoh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upstream's &lt;code&gt;Gotoh&lt;/code&gt; runs its dynamic programming on numpy matrices of shape &lt;code&gt;(len_s1+1, len_s2+1)&lt;/code&gt;. When exactly one input is empty, that matrix has a single row or a single column, and the initialization loop writes &lt;code&gt;p_mat[1, j]&lt;/code&gt;, which is row 1 of a one-row matrix. Crash. &lt;code&gt;gotoh('', '')&lt;/code&gt; is fine and &lt;code&gt;gotoh('x', 'y')&lt;/code&gt; is fine. The single-empty case just explodes.&lt;/p&gt;

&lt;p&gt;Why is it &lt;strong&gt;latent&lt;/strong&gt;? Two reasons. Upstream's own tests never pass exactly one empty string, so no test reaches that path. And my broken harness was never value-comparing gotoh at all, so I could not have found it anyway. A real bug revealed by fixing the tooling.&lt;/p&gt;

&lt;p&gt;Now the porting-philosophy moment: do I fix it? No. Behavioral parity means the port must reproduce it byte-for-byte. &lt;code&gt;gotoh('', 'x')&lt;/code&gt; raises the same &lt;code&gt;IndexError&lt;/code&gt;, and a test pins it so nobody "helpfully" patches it out later. The finding is documented in &lt;a href="https://github.com/Amitk003/textdistance-rust/blob/main/BUG.md" rel="noopener noreferrer"&gt;&lt;code&gt;BUG.md&lt;/code&gt;&lt;/a&gt; and the upstream report is ready to file. A port that silently "fixes" this is &lt;em&gt;less&lt;/em&gt; faithful, not more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: the byte war over LZMA
&lt;/h2&gt;

&lt;p&gt;The compression metrics compute NCD from compressed lengths, so they depend on the exact bytes &lt;code&gt;liblzma&lt;/code&gt; emits. My first implementation used the one-shot &lt;code&gt;lzma_stream_buffer_encode&lt;/code&gt;. CPython's &lt;code&gt;lzma.compress&lt;/code&gt; drives the &lt;em&gt;streaming&lt;/em&gt; &lt;code&gt;lzma_stream_encoder&lt;/code&gt; plus &lt;code&gt;lzma_code(LZMA_FINISH)&lt;/code&gt;. Both call the same vendored &lt;code&gt;liblzma 5.2.5&lt;/code&gt;, so I assumed the outputs matched.&lt;/p&gt;

&lt;p&gt;They do not. The block header differs, the LZMA2 payload differs, and for one repeated-string input the compressed length came out &lt;strong&gt;58 vs 62 bytes&lt;/strong&gt;, which changes the NCD value and, with it, the whole algorithm's result. This one cost hours because it only showed up on specific inputs.&lt;/p&gt;

&lt;p&gt;I killed it with a Rust experiment: a tiny example binary compressing the same inputs through both APIs and dumping lengths and bytes. Streaming was byte-identical to CPython. The codec now uses the streaming path and pins CPython's header-trimmed lengths in a unit test. Two more LZMA gremlins showed up on the way: &lt;code&gt;lzma_lzma_preset&lt;/code&gt; returning 0 on the toolchain while still populating &lt;code&gt;dict_size&lt;/code&gt; (an assert panicked at startup; the fix is to check the options, not the return value), and the whole problem only mattering because the CI Python and the local Python happened to bundle different &lt;code&gt;liblzma&lt;/code&gt; builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: quirk-for-quirks is a menu, not a bug list
&lt;/h2&gt;

&lt;p&gt;The fuzzer then taught me what "behavioral port" really means, one quirk at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;strcmp95&lt;/strong&gt;: the matching, transposition, and similarity loops each deviated from the original. The similarity pass rewards only the &lt;em&gt;phonetic&lt;/em&gt; pairs from the &lt;code&gt;adjwt&lt;/code&gt; table, not plain character equality. And the transposition pass reuses the matching loop's final &lt;code&gt;j&lt;/code&gt; and falls back to &lt;code&gt;s2[len_s2-1]&lt;/code&gt; when no flagged position is found. You cannot guess this. You have to transcribe it. A 12-pair probe went from 10/12 to 12/12.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hamming on lists&lt;/strong&gt;: the original pads with &lt;code&gt;None&lt;/code&gt; via &lt;code&gt;zip_longest&lt;/code&gt; and compares with Python &lt;code&gt;==&lt;/code&gt;, so &lt;code&gt;None == None&lt;/code&gt; &lt;em&gt;matches&lt;/em&gt;. My Rust kernel treated padding as an automatic mismatch and even distinguished Rust's &lt;code&gt;Option::None&lt;/code&gt; from an actual &lt;code&gt;None&lt;/code&gt; element. Padding now maps to Python &lt;code&gt;None&lt;/code&gt;, and everything compares through &lt;code&gt;==&lt;/code&gt;. This also fixed MLIPNS, which delegates to Hamming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BWTRLENCD on a list&lt;/strong&gt;: the original appends a &lt;code&gt;'\0'&lt;/code&gt; terminator and then calls &lt;code&gt;type(data)().join(...)&lt;/code&gt;. On a list that is &lt;code&gt;list().join(...)&lt;/code&gt;, which raises &lt;code&gt;AttributeError&lt;/code&gt;. The port returned a value. Now it raises the identical &lt;code&gt;AttributeError&lt;/code&gt;. Faithfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLIPNS return type&lt;/strong&gt;: the original returns &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; as &lt;code&gt;int&lt;/code&gt; on every path. My port returned the &lt;code&gt;f64&lt;/code&gt; kernel value. &lt;code&gt;1.0 == 1&lt;/code&gt; numerically, so a type-insensitive comparison never caught it. Now it is narrowed to &lt;code&gt;int&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem 5: the environment fight (numpy, everywhere)
&lt;/h2&gt;

&lt;p&gt;The CI differential-fuzz step failed with about 2,025 divergences. Locally it was zero. The count was a clue: 3 x (25000/37). Exactly three algorithms, diverging on &lt;em&gt;every&lt;/em&gt; case: &lt;code&gt;NeedlemanWunsch&lt;/code&gt;, &lt;code&gt;SmithWaterman&lt;/code&gt;, &lt;code&gt;Gotoh&lt;/code&gt;. Upstream implements all three in numpy, and at the top of each &lt;code&gt;__call__&lt;/code&gt; it does &lt;code&gt;if not numpy: raise ImportError(...)&lt;/code&gt;. The CI runner had no numpy, so the &lt;em&gt;reference&lt;/em&gt; raised on every call while my Rust kernels happily computed values. Locally numpy was installed, so both sides computed. Environment, not code.&lt;/p&gt;

&lt;p&gt;The fix was philosophically interesting. I did not just install numpy on CI (that only patches the environment). I mirrored the dependency in the port: same guarded &lt;code&gt;import numpy&lt;/code&gt;, same &lt;code&gt;ImportError&lt;/code&gt;, same message. Now with numpy installed, both compute and match. Without it, both raise identically. The port behaves like the original in &lt;em&gt;every&lt;/em&gt; environment, and the fuzzer stays honest.&lt;/p&gt;

&lt;p&gt;That one change then broke the test suite on CI, because the pinned upstream tests exercise those three algorithms and need numpy to compute. So the CI workflow needed numpy too. And then &lt;code&gt;build.ps1&lt;/code&gt; needed numpy too. Three separate fixes for one environmental truth, each one discovered by a CI run that was, in hindsight, doing its job. The CI whack-a-mole (fmt, then clippy &lt;code&gt;-D warnings&lt;/code&gt;, then numpy) was annoying, but it is exactly what a pipeline is for: it kept failing until the environment and the port agreed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark that disappointed me
&lt;/h2&gt;

&lt;p&gt;I wanted to print a clean "bit-identical on everything." Reality is messier, and publishing the mess is the point of this write-up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The fuzzer's &lt;code&gt;0 divergences&lt;/code&gt; comes with &lt;strong&gt;4,140 + 5,504 near-misses within 1e-9&lt;/strong&gt;. These are floating-point repr differences I cannot scrub away (numpy computes some values differently than my Rust &lt;code&gt;f64&lt;/code&gt;). I report them. I do not hide them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gotoh('', 'x')&lt;/code&gt; crashes on purpose. That is a feature of a faithful port and a bug in upstream. Both are true.&lt;/li&gt;
&lt;li&gt;The CLI diff is &lt;strong&gt;0 numeric diffs on 792 inputs&lt;/strong&gt;, plus 4 cases where the &lt;em&gt;original&lt;/em&gt; raises and my CLI answers. Faithful, and weird to ship.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the performance table has a row that hurts. The edit and sequence kernels are 6 to 330 times faster (&lt;code&gt;levenshtein&lt;/code&gt; 6x, &lt;code&gt;damerau_levenshtein&lt;/code&gt; 10x, &lt;code&gt;lcsseq&lt;/code&gt; 12x, &lt;code&gt;levenshtein&lt;/code&gt; on long strings &lt;strong&gt;331x&lt;/strong&gt;, &lt;code&gt;lcsseq&lt;/code&gt; on long strings &lt;strong&gt;199x&lt;/strong&gt;). But the compression family, the whole reason I vendored &lt;code&gt;liblzma&lt;/code&gt;, is basically flat: &lt;code&gt;bz2_ncd&lt;/code&gt; is &lt;strong&gt;1.09x&lt;/strong&gt;. The bottleneck there is the compression itself, not the Python glue, and Rust does not make &lt;code&gt;libbz2&lt;/code&gt; faster. I wrote that number down anyway, because "ported to Rust, everything is faster" is the claim that this row disproves. (The good news in the same table: the port imports in 15ms vs 90ms, and uses about 17MB of RAM vs 27MB.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The unsafe block I could not remove
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;honest_report.py&lt;/code&gt; counts &lt;strong&gt;10 &lt;code&gt;unsafe&lt;/code&gt; blocks, all of them in the &lt;code&gt;codec&lt;/code&gt; C-wrapper crate&lt;/strong&gt;, the FFI to vendored &lt;code&gt;liblzma&lt;/code&gt; and &lt;code&gt;libbz2&lt;/code&gt;. The Rust core (&lt;code&gt;tdcore&lt;/code&gt;), the PyO3 glue (&lt;code&gt;pyapi&lt;/code&gt;), and the CLI (&lt;code&gt;tdc&lt;/code&gt;) are &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;: zero blocks. I could not remove the ten because you cannot call C from Rust without &lt;code&gt;unsafe&lt;/code&gt;. The discipline is containment: the unsafe is exactly where the C is, and nowhere else. I would rather point at the line than pretend it does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would take back
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trusting the first "0 divergences."&lt;/strong&gt; The most expensive mistake was believing my own tool before auditing it. The harness is the crown jewel of this project. It should have been built first and audited first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not standardizing the dev and CI environment sooner.&lt;/strong&gt; numpy, the local &lt;code&gt;liblzma&lt;/code&gt;, the build script: three separate fights for one lesson. The reference and the port must run in identical environments, or the comparison is meaningless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce-don't-fix for gotoh.&lt;/strong&gt; I would defend this one, not take it back. But I underestimated how much it costs to explain "my port crashes on purpose, and that is correct" to people who glance at a README. The documentation does the heavy lifting.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest close
&lt;/h2&gt;

&lt;p&gt;What this port is: the original's own suite passing unmodified; 160,000+ fresh random cases with zero divergence; the bugs in the tooling that proved it; the deviating behaviors documented as deliberate, not swept under the rug; and a benchmark table that includes its own embarrassment. What it is not: "Rust made everything faster." It made the edit distances absurdly faster and left the compression metrics exactly where they were. Both of those are true.&lt;/p&gt;

&lt;p&gt;If you are porting something, port the behavior, not the happy path. And before you trust your fuzzer, fuzz the fuzzer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Code and all the numbers behind this: &lt;a href="https://github.com/Amitk003/textdistance-rust" rel="noopener noreferrer"&gt;github.com/Amitk003/textdistance-rust&lt;/a&gt;. The differential harness is in &lt;code&gt;fuzz/&lt;/code&gt;, the honest-number scripts in &lt;code&gt;scripts/&lt;/code&gt; and &lt;code&gt;bench/&lt;/code&gt;, and the documented latent bug is in &lt;a href="https://github.com/Amitk003/textdistance-rust/blob/main/BUG.md" rel="noopener noreferrer"&gt;&lt;code&gt;BUG.md&lt;/code&gt;&lt;/a&gt;. Written for the Hackathon Raptors side quest.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathonraptors</category>
      <category>rust</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
