<?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: codewitharyan29</title>
    <description>The latest articles on DEV Community by codewitharyan29 (@codewitharyan29).</description>
    <link>https://dev.to/codewitharyan29</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%2F4065878%2F04861b36-4758-40cc-88c6-b96cee0bd97f.png</url>
      <title>DEV Community: codewitharyan29</title>
      <link>https://dev.to/codewitharyan29</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codewitharyan29"/>
    <language>en</language>
    <item>
      <title>Porting natsort to Rust taught me that "it compiles" is the easy 5%</title>
      <dc:creator>codewitharyan29</dc:creator>
      <pubDate>Thu, 06 Aug 2026 14:44:40 +0000</pubDate>
      <link>https://dev.to/codewitharyan29/porting-natsort-to-rust-taught-me-that-it-compiles-is-the-easy-5-2ac3</link>
      <guid>https://dev.to/codewitharyan29/porting-natsort-to-rust-taught-me-that-it-compiles-is-the-easy-5-2ac3</guid>
      <description>&lt;p&gt;TL;DR: I ported Python's natsort library to Rust for Port Mortem 2026.&lt;/p&gt;

&lt;p&gt;Writing the sorting algorithm turned out to be the easy part.&lt;/p&gt;

&lt;p&gt;The difficult part was proving the Rust implementation behaved &lt;strong&gt;identically&lt;/strong&gt; to the original Python library across thousands of edge cases without quietly changing behavior.&lt;/p&gt;

&lt;p&gt;That meant treating verification as the real project.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why I chose natsort&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most demo ports are calculators, parsers, or utilities with a handful of tests.&lt;/p&gt;

&lt;p&gt;I wanted something that would actually stress the verification process.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;natsort&lt;/code&gt; sorts strings the way humans expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file2
file10
file20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of lexicographic order.&lt;/p&gt;

&lt;p&gt;That sounds simple until you discover it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seven algorithm variants&lt;/li&gt;
&lt;li&gt;signed and floating-point numbers&lt;/li&gt;
&lt;li&gt;scientific notation&lt;/li&gt;
&lt;li&gt;full Unicode numeric characters&lt;/li&gt;
&lt;li&gt;locale-aware behavior&lt;/li&gt;
&lt;li&gt;Roman numerals&lt;/li&gt;
&lt;li&gt;fractions&lt;/li&gt;
&lt;li&gt;a real upstream test suite covering years of edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A port that looks correct on five examples proves almost nothing.&lt;/p&gt;

&lt;p&gt;A port that survives the library's own test suite, differential fuzzing, CLI comparisons, property testing, and mutation testing starts becoming evidence.&lt;/p&gt;




&lt;h1&gt;
  
  
  The architecture
&lt;/h1&gt;

&lt;p&gt;The verification pipeline ended up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Python Test Suite
                    │
                    ▼
          Thin Python Adapter
                    │
                    ▼
             Compiled Rust Binary
                    │
                    ▼
          Rust natsort Implementation
                    │
                    ▼
 Compare:
 • stdout
 • stderr
 • exit codes
 • ordering
 • fuzz results
 • properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter intentionally contains almost no logic.&lt;/p&gt;

&lt;p&gt;Its only job is forwarding Python test inputs to the Rust implementation.&lt;/p&gt;

&lt;p&gt;That design choice turned out to matter more than I expected.&lt;/p&gt;




&lt;h1&gt;
  
  
  What broke (and why those bugs mattered)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Differential fuzzing found a bug in the original library
&lt;/h2&gt;

&lt;p&gt;Differential fuzzing generates thousands of random inputs and compares both implementations.&lt;/p&gt;

&lt;p&gt;Most mismatches were my mistakes.&lt;/p&gt;

&lt;p&gt;One wasn't.&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="nf"&gt;natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e400&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;1e500&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&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;1e400&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;1e500&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e500&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;1e400&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&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;1e500&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;1e400&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both calls succeed.&lt;/p&gt;

&lt;p&gt;Both disagree.&lt;/p&gt;

&lt;p&gt;The reason is surprisingly simple.&lt;/p&gt;

&lt;p&gt;Both values overflow to floating-point infinity.&lt;/p&gt;

&lt;p&gt;Since both become &lt;code&gt;inf&lt;/code&gt;, the comparison reports them equal, and Python's stable sort simply preserves whichever order the inputs arrived in.&lt;/p&gt;

&lt;p&gt;Rust's &lt;code&gt;f64&lt;/code&gt; behaves the same way.&lt;/p&gt;

&lt;p&gt;That means the correct port is &lt;strong&gt;not&lt;/strong&gt; one that "fixes" the behavior.&lt;/p&gt;

&lt;p&gt;The correct port reproduces it.&lt;/p&gt;

&lt;p&gt;I reported the issue upstream:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;natsort#192&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding a real bug in a mature library simply by holding another implementation beside it was one of the most satisfying moments of the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. My adapter was lying to me
&lt;/h2&gt;

&lt;p&gt;I assumed the adapter was a thin wrapper.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;One function calculated the correct Rust-backed answer...&lt;/p&gt;

&lt;p&gt;...and then ignored it.&lt;/p&gt;

&lt;p&gt;Instead it silently fell back to a handwritten Python implementation that didn't understand signed numbers or Unicode.&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="nf"&gt;index_natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-5&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;-1&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;3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;correct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Differential testing doesn't automatically catch bugs in the adapter.&lt;/p&gt;

&lt;p&gt;You have to verify the adapter itself.&lt;/p&gt;

&lt;p&gt;That lesson probably saved me more time than any optimization.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Windows disagreed with Linux
&lt;/h2&gt;

&lt;p&gt;Everything looked perfect.&lt;/p&gt;

&lt;p&gt;Until I ran it on Windows.&lt;/p&gt;

&lt;p&gt;Five bugs immediately appeared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;executable needed &lt;code&gt;.exe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python3&lt;/code&gt; didn't exist (&lt;code&gt;py&lt;/code&gt; does)&lt;/li&gt;
&lt;li&gt;subprocess output defaulted to CP-1252 instead of UTF-8&lt;/li&gt;
&lt;li&gt;file reads had the same encoding problem&lt;/li&gt;
&lt;li&gt;Unix-only &lt;code&gt;resource&lt;/code&gt; crashed benchmarks entirely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those appeared on Linux.&lt;/p&gt;

&lt;p&gt;None appeared in my development environment.&lt;/p&gt;

&lt;p&gt;Every one appeared on a real Windows machine.&lt;/p&gt;

&lt;p&gt;"Works on my machine" turned out to be the weakest verification strategy of all.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. GitHub Actions lied with a green checkmark
&lt;/h2&gt;

&lt;p&gt;The deployment workflow looked successful.&lt;/p&gt;

&lt;p&gt;The live website returned 404.&lt;/p&gt;

&lt;p&gt;The culprit?&lt;/p&gt;

&lt;p&gt;A multi-line shell block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl ... | sh
build
deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installation silently failed.&lt;/p&gt;

&lt;p&gt;The following commands still exited successfully.&lt;/p&gt;

&lt;p&gt;GitHub reported the entire step as green.&lt;/p&gt;

&lt;p&gt;The fix wasn't clever.&lt;/p&gt;

&lt;p&gt;It was boring.&lt;/p&gt;

&lt;p&gt;I switched to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;wasm-pack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and added a deployment check that refuses to publish unless the expected build artifacts actually exist.&lt;/p&gt;

&lt;p&gt;A green checkmark only matters if it's checking the thing you care about.&lt;/p&gt;




&lt;h1&gt;
  
  
  How I proved behavioral equivalence
&lt;/h1&gt;

&lt;p&gt;The implementation wasn't the deliverable.&lt;/p&gt;

&lt;p&gt;Evidence was.&lt;/p&gt;

&lt;p&gt;I ended up with six independent layers of verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Upstream tests
&lt;/h3&gt;

&lt;p&gt;Run the original &lt;strong&gt;natsort&lt;/strong&gt; test suite without modifying it.&lt;/p&gt;

&lt;p&gt;Tests excluded only when Rust genuinely cannot reproduce Python-specific behavior, and every exclusion is documented.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Differential fuzzing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;27,000&lt;/strong&gt; seeded comparisons across all algorithm variants.&lt;/p&gt;

&lt;p&gt;Same inputs.&lt;/p&gt;

&lt;p&gt;Same outputs.&lt;/p&gt;

&lt;p&gt;Reproducible failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. CLI differential testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2,800&lt;/strong&gt; CLI invocations comparing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stdout&lt;/li&gt;
&lt;li&gt;stderr&lt;/li&gt;
&lt;li&gt;exit codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Matching output alone isn't enough.&lt;/p&gt;

&lt;p&gt;Programs communicate failure through exit codes too.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Property testing
&lt;/h3&gt;

&lt;p&gt;Randomized verification of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotence&lt;/li&gt;
&lt;li&gt;antisymmetry&lt;/li&gt;
&lt;li&gt;transitivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These find classes of bugs that handwritten examples never will.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Mutation testing
&lt;/h3&gt;

&lt;p&gt;I deliberately broke my implementation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reversed comparisons&lt;/li&gt;
&lt;li&gt;removed signs&lt;/li&gt;
&lt;li&gt;altered parsing logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the verification pipeline couldn't detect injected bugs, it wasn't trustworthy.&lt;/p&gt;

&lt;p&gt;Every injected mutation was caught.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Honest benchmarking
&lt;/h3&gt;

&lt;p&gt;Instead of publishing a single "10× faster" headline, I reported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mean&lt;/li&gt;
&lt;li&gt;median&lt;/li&gt;
&lt;li&gt;p95&lt;/li&gt;
&lt;li&gt;p99&lt;/li&gt;
&lt;li&gt;peak memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real result was &lt;strong&gt;6–8× faster&lt;/strong&gt;, depending on workload.&lt;/p&gt;

&lt;p&gt;Honest numbers build more confidence than inflated ones.&lt;/p&gt;




&lt;h1&gt;
  
  
  The decision I'd take back
&lt;/h1&gt;

&lt;p&gt;I spent a huge amount of time chasing perfect Unicode parity.&lt;/p&gt;

&lt;p&gt;It was technically rewarding.&lt;/p&gt;

&lt;p&gt;It pushed differential matching from roughly &lt;strong&gt;43%&lt;/strong&gt; to &lt;strong&gt;100%&lt;/strong&gt; across the full Unicode character set.&lt;/p&gt;

&lt;p&gt;I'm proud of that work.&lt;/p&gt;

&lt;p&gt;But I crossed the point of diminishing returns.&lt;/p&gt;

&lt;p&gt;One adapter-level discrepancy survived until the deadline on a single CI runner.&lt;/p&gt;

&lt;p&gt;I excluded that specific test rather than claim support I couldn't fully defend.&lt;/p&gt;

&lt;p&gt;Looking back, I'd stop polishing Unicode earlier and spend those hours investigating the remaining verification discrepancy instead.&lt;/p&gt;

&lt;p&gt;Verification gets exponentially more expensive near the finish line.&lt;/p&gt;

&lt;p&gt;Knowing when to stop is part of engineering.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final numbers
&lt;/h1&gt;

&lt;p&gt;By the end of the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Original upstream tests running against Rust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;27,000&lt;/strong&gt; differential fuzz comparisons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2,800&lt;/strong&gt; CLI differential runs&lt;/li&gt;
&lt;li&gt;Property testing&lt;/li&gt;
&lt;li&gt;Mutation testing&lt;/li&gt;
&lt;li&gt;Honest benchmark suite&lt;/li&gt;
&lt;li&gt;6–8× speedup&lt;/li&gt;
&lt;li&gt;One upstream bug reported&lt;/li&gt;
&lt;li&gt;Cross-platform verification on Linux and Windows&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Reproduce everything
&lt;/h1&gt;

&lt;p&gt;Everything needed to verify the project is public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/jVz_2AP4U4w" rel="noopener noreferrer"&gt;https://youtu.be/jVz_2AP4U4w&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live WASM demo + verification dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codewitharyan29.github.io/Port-Mortem/" rel="noopener noreferrer"&gt;https://codewitharyan29.github.io/Port-Mortem/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codewitharyan29/Port-Mortem" rel="noopener noreferrer"&gt;https://github.com/codewitharyan29/Port-Mortem&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upstream issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SethMMorton/natsort/issues/192" rel="noopener noreferrer"&gt;https://github.com/SethMMorton/natsort/issues/192&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Closing thoughts
&lt;/h1&gt;

&lt;p&gt;Anyone with an AI coding agent can produce something that compiles.&lt;/p&gt;

&lt;p&gt;Compilation is the easy part.&lt;/p&gt;

&lt;p&gt;The difficult part is proving—without hand-waving—that another implementation behaves the same as the original across thousands of edge cases, different operating systems, different execution paths, and even the original project's own bugs.&lt;/p&gt;

&lt;p&gt;By the end of this project, I stopped measuring success by whether the Rust code compiled.&lt;/p&gt;

&lt;p&gt;I measured success by how much evidence I had that it behaved like Python.&lt;/p&gt;

&lt;p&gt;That, more than the port itself, is what I learned from Port Mortem 2026.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;#PortMortem2026 #HackathonRaptors #Rust #Python #OpenSource #SystemsProgramming #Testing #Verification #Fuzzing&lt;/em&gt;&lt;br&gt;
**&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>python</category>
      <category>rust</category>
      <category>software</category>
    </item>
  </channel>
</rss>
