<?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: Pranav Gupta</title>
    <description>The latest articles on DEV Community by Pranav Gupta (@pranav-dev).</description>
    <link>https://dev.to/pranav-dev</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%2F4046984%2F943eba14-9db5-48c5-87eb-8aeeed5e801e.jpg</url>
      <title>DEV Community: Pranav Gupta</title>
      <link>https://dev.to/pranav-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pranav-dev"/>
    <language>en</language>
    <item>
      <title>I Ported python-semanticversion to Rust in 72 Hours: What Broke, How I Proved Parity, and the 6-Hour Edge Case</title>
      <dc:creator>Pranav Gupta</dc:creator>
      <pubDate>Wed, 05 Aug 2026 21:29:53 +0000</pubDate>
      <link>https://dev.to/pranav-dev/i-ported-python-semanticversion-to-rust-in-72-hours-5e83</link>
      <guid>https://dev.to/pranav-dev/i-ported-python-semanticversion-to-rust-in-72-hours-5e83</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Port Mortem 2026 Write-Up Submission&lt;/strong&gt; · Track D (Python → Rust) · Solo build by &lt;a href="https://github.com/rahulgupta0-dev" rel="noopener noreferrer"&gt;Rahul Gupta&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When participating in &lt;strong&gt;Port Mortem 2026&lt;/strong&gt; (a 72-hour hackathon to resurrect and rewrite dead or legacy libraries in memory-safe languages), I picked &lt;a href="https://github.com/rbarrois/python-semanticversion" rel="noopener noreferrer"&gt;&lt;code&gt;python-semanticversion&lt;/code&gt;&lt;/a&gt; for &lt;strong&gt;Track D (Python → Rust)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python-semanticversion&lt;/code&gt; is a foundational Python package implementing full SemVer 2.0 parsing, comparison, and npm-style range specification (&lt;code&gt;SimpleSpec&lt;/code&gt;, &lt;code&gt;NpmSpec&lt;/code&gt;, and &lt;code&gt;LegacySpec&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Below is the honest post-mortem of how I ported it to 100% safe Rust (&lt;code&gt;semanticversion-rs&lt;/code&gt;), proved behavioral equivalence, fixed subtle edge-case bugs, survived a 6-hour AST set-equality nightmare, and what I’d do differently next time.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What I Picked &amp;amp; The Strategy
&lt;/h2&gt;

&lt;p&gt;The goal wasn't just to write a Rust library that &lt;em&gt;looked&lt;/em&gt; like SemVer parsing—it was to create a drop-in replacement that could execute the &lt;strong&gt;original Python test suite unmodified&lt;/strong&gt; while delivering multi-fold speedups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Stack &amp;amp; Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: 100% Safe Rust (&lt;code&gt;grep -rn unsafe src/&lt;/code&gt; returns 0 results).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension Bridge&lt;/strong&gt;: PyO3 + Maturin producing a native Python extension named &lt;code&gt;semantic_version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing&lt;/strong&gt;: &lt;code&gt;regex&lt;/code&gt; crate for version component extraction + custom recursive-descent parser for complex range grammar trees (&lt;code&gt;Clause::AnyOf&lt;/code&gt;, &lt;code&gt;Clause::AllOf&lt;/code&gt;, &lt;code&gt;Clause::Range&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. How I Proved Behavioral Equivalence
&lt;/h2&gt;

&lt;p&gt;Claiming a port is "100% compatible" without proof is easy. To ensure zero behavioral divergence, I used a &lt;strong&gt;three-tier verification pipeline&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: Zero-Edit Pytest Verification
&lt;/h3&gt;

&lt;p&gt;Instead of porting tests to Rust assertions, I compiled the Rust engine as a PyO3 native module (&lt;code&gt;semantic_version&lt;/code&gt;). Running &lt;code&gt;pytest tests/original&lt;/code&gt; executes the &lt;strong&gt;original, unmodified test suite&lt;/strong&gt; directly against compiled Rust code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="c"&gt;# maturin develop &amp;amp;&amp;amp; pytest tests/original -q&lt;/span&gt;
&lt;span class="c"&gt;# Result: 54 passed, 16 skipped ("Django not installed"), 586 subtests green&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Zero lines of Python test code were modified.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2: 24,500-Pair Deterministic Differential Fuzzing
&lt;/h3&gt;

&lt;p&gt;I built a seed-based differential oracle (&lt;code&gt;fuzz/differential/driver.py&lt;/code&gt;) that generated 24,500 random &lt;code&gt;Version&lt;/code&gt;, &lt;code&gt;SimpleSpec&lt;/code&gt;, and &lt;code&gt;NpmSpec&lt;/code&gt; inputs across both the original Python reference environment and the PyO3 Rust extension.&lt;/p&gt;

&lt;p&gt;Field-by-field verification evaluated &lt;code&gt;major&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;prerelease&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;repr&lt;/code&gt;, &lt;code&gt;partial&lt;/code&gt;, &lt;code&gt;valid&lt;/code&gt;, &lt;code&gt;compare&lt;/code&gt;, and &lt;code&gt;matches&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard Behavioral Divergences&lt;/strong&gt;: &lt;strong&gt;0&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft Wording Differences&lt;/strong&gt; (error message text formatting): &lt;strong&gt;1,619&lt;/strong&gt; (expected minor string formatting differences).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tier 3: 2.55 Million Run Crash Fuzzing
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;cargo-fuzz&lt;/code&gt; / &lt;code&gt;libFuzzer&lt;/code&gt; targeting raw byte slices fed into all entry points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total Executions&lt;/strong&gt;: &lt;strong&gt;2,554,822 runs&lt;/strong&gt; in 61 seconds (~41,882 exec/sec).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Panics / Crashes&lt;/strong&gt;: &lt;strong&gt;0&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. What Broke: 8 Latent &amp;amp; Port Bugs Caught by Fuzzing
&lt;/h2&gt;

&lt;p&gt;Fuzzing didn't just verify parity; it caught 8 critical bugs during development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;u64&lt;/code&gt; Arithmetic Overflow Panics&lt;/strong&gt;: In expressions like &lt;code&gt;patch + 1&lt;/code&gt;, a version like &lt;code&gt;1.2.18446744073709551615&lt;/code&gt; caused a panic.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Replaced all 18 addition sites across &lt;code&gt;version.rs&lt;/code&gt;, &lt;code&gt;simple_spec.rs&lt;/code&gt;, and &lt;code&gt;npm_spec.rs&lt;/code&gt; with &lt;code&gt;saturating_add(1)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty Prerelease Identifiers (&lt;code&gt;1.2.3-..&lt;/code&gt;)&lt;/strong&gt;: Python rejected empty identifier tokens (&lt;code&gt;..&lt;/code&gt;), whereas Rust initially accepted them.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Enforced strict token validation in &lt;code&gt;parse_prerelease_identifiers&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty &lt;code&gt;||&lt;/code&gt; Group Substitution&lt;/strong&gt;: Python's &lt;code&gt;NpmSpec&lt;/code&gt; parser turns empty &lt;code&gt;||&lt;/code&gt; clauses into &lt;code&gt;&amp;gt;=0.0.0&lt;/code&gt;, while Rust initially evaluated them as &lt;code&gt;Clause::Never&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Added zero-length group fallback logic to &lt;code&gt;AnyOf([AllOf([&amp;gt;=0.0.0])])&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wildcard Rejection on &lt;code&gt;~*&lt;/code&gt; / &lt;code&gt;^*&lt;/code&gt;&lt;/strong&gt;: Python explicitly rejects caret/tilde modifiers combined with wildcard majors.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Inserted a validation gate prior to caret/tilde range expansion.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hyphen Range Bound Fences&lt;/strong&gt;: &lt;code&gt;1.0.0-rc.1 - 2.0.0&lt;/code&gt; used &lt;code&gt;&amp;gt;&lt;/code&gt; for fence lower-bounds instead of &lt;code&gt;&amp;gt;= major.minor.0&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Passed &lt;code&gt;is_upper_bound&lt;/code&gt; context into &lt;code&gt;expand_prerelease_or_hyphen&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equality vs. Ordering Semantics&lt;/strong&gt;: SemVer 2.0 dictates that &lt;code&gt;1.0.0&lt;/code&gt; and &lt;code&gt;1.0.0+build&lt;/code&gt; compare &lt;strong&gt;equal&lt;/strong&gt; for precedence sorting, but are &lt;strong&gt;not equal&lt;/strong&gt; for string equality.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Custom &lt;code&gt;PartialEq&lt;/code&gt; comparing all fields (including build), while &lt;code&gt;Ord&lt;/code&gt; / &lt;code&gt;PartialOrd&lt;/code&gt; uses precedence keys ignoring build metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  4. The Edge Case That Ate 6 Hours: Python &lt;code&gt;frozenset&lt;/code&gt; ASTs vs. Rust &lt;code&gt;Vec&amp;lt;Clause&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The single hardest bug encountered during the 72 hours involved AST node equality in &lt;code&gt;NpmSpec&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Python's &lt;code&gt;semantic_version&lt;/code&gt;, &lt;code&gt;Clause&lt;/code&gt; nodes (&lt;code&gt;AllOf&lt;/code&gt;, &lt;code&gt;AnyOf&lt;/code&gt;) store child clauses in &lt;strong&gt;&lt;code&gt;frozenset&lt;/code&gt; instances&lt;/strong&gt; (&lt;code&gt;base.py:745, 808&lt;/code&gt;). As a result, AST equality in Python is &lt;strong&gt;order-insensitive and auto-deduplicating&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;$$\text{AllOf}([A, B]) == \text{AllOf}([B, A])$$&lt;/p&gt;

&lt;p&gt;In Rust, however, &lt;code&gt;AllOf(Vec&amp;lt;Clause&amp;gt;)&lt;/code&gt; uses standard vector comparison.&lt;/p&gt;

&lt;p&gt;When parsing caret ranges like &lt;code&gt;^1.2.3&lt;/code&gt;, Python emitted &lt;code&gt;AllOf([LT 2.0.0, GTE 1.2.3])&lt;/code&gt;, while Rust's parser generated &lt;code&gt;AllOf([GTE 1.2.3, LT 2.0.0])&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When running pytest, assertions like:&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="k"&gt;assert&lt;/span&gt; &lt;span class="nc"&gt;NpmSpec&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.2.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;clause&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;NpmSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=1.2.3 &amp;lt;2.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;clause&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;failed miserably! The versions matched identically, but the underlying &lt;strong&gt;AST clause trees failed equality checks&lt;/strong&gt; simply because child nodes were ordered differently in the vector.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Instead of forcing Rust's internal parser to match Python's exact construction sequence for every grammar edge case, I implemented &lt;strong&gt;set-equality and dedup hashing&lt;/strong&gt; inside PyO3's &lt;code&gt;Clause&lt;/code&gt; binding layer (&lt;code&gt;src/bindings.rs&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Custom set-equality for PyO3 Clause objects to mirror frozenset semantics&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;clause_eq_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;AllOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_nodes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nn"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;AllOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b_nodes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;AnyOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_nodes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nn"&gt;Clause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;AnyOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b_nodes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;set_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_nodes&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b_nodes&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;set_a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;set_b&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving this unlocked 100% pass rates across &lt;code&gt;test_spec.py&lt;/code&gt; without modifying native Rust performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Decision I'd Take Back: PyO3 Tuple Allocations for Precedence Comparison (D17)
&lt;/h2&gt;

&lt;p&gt;If I could redo one decision from the hackathon, it would be &lt;strong&gt;Decision D17: PyO3 Precedence Key Returns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To implement Python's &lt;code&gt;_cmp_precedence_key&lt;/code&gt; dunder attribute, the PyO3 binding layer constructs Python tuples on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rust binding allocating a PyTuple on every precedence key read:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pre_tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;build_tuple&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_py&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Performance Cost
&lt;/h3&gt;

&lt;p&gt;Because Python's comparison operators (&lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;) rely on comparing these tuple objects, comparison operations executed from Python through PyO3 incurred heap allocation overhead:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark Scenario&lt;/th&gt;
&lt;th&gt;Latency / Throughput&lt;/th&gt;
&lt;th&gt;Speedup vs Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Native Rust &lt;code&gt;precedence_lt&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;386 ns p50&lt;/strong&gt; (2.3 Million ops/sec)&lt;/td&gt;
&lt;td&gt;N/A (Pure Rust)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PyO3 &lt;code&gt;_cmp_precedence_key&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python tuple construction overhead&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.27×&lt;/strong&gt; (Slower than Python)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While parsing and matching achieved &lt;strong&gt;11× to 60× speedups&lt;/strong&gt;, Python-to-Rust comparison throughput lagged due to object allocations at the boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Should Have Done
&lt;/h3&gt;

&lt;p&gt;Instead of delegating comparison logic back to Python tuple comparison, I should have implemented &lt;code&gt;__richcmp__&lt;/code&gt; natively in C/Rust using &lt;code&gt;Version::cmp_precedence_key()&lt;/code&gt; directly, bypassing Python tuple creation entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Final Benchmark Summary
&lt;/h2&gt;

&lt;p&gt;Benchmarked on an idle Linux VM (Intel x86_64 @ 2.20GHz, 2 physical cores, Rust 1.96, Python 3.11):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Rust (Native p50)&lt;/th&gt;
&lt;th&gt;Python Baseline&lt;/th&gt;
&lt;th&gt;Speedup / Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Version::parse&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.91 µs&lt;/td&gt;
&lt;td&gt;21.0 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;SimpleSpec::parse&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.79 µs&lt;/td&gt;
&lt;td&gt;34.7 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;NpmSpec::parse&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.51 µs&lt;/td&gt;
&lt;td&gt;126.6 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;match_version&lt;/code&gt; (npm)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.52 µs&lt;/td&gt;
&lt;td&gt;91.2 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peak Memory RSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.5 MB&lt;/td&gt;
&lt;td&gt;15.9 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21% memory reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Code
&lt;/h2&gt;

&lt;p&gt;Porting dead or legacy code isn't just about translating lines of syntax—it's about honoring original behavioral semantics while eliminating memory safety risks and performance bottlenecks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/rahulgupta0-dev/semanticversion-rs" rel="noopener noreferrer"&gt;&lt;code&gt;rahulgupta0-dev/semanticversion-rs&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision Log&lt;/strong&gt;: &lt;a&gt;&lt;code&gt;DECISIONS.md&lt;/code&gt;&lt;/a&gt; (20 detailed entries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submission Spec&lt;/strong&gt;: &lt;a&gt;&lt;code&gt;.port-mortem.toml&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Special thanks to Hackathon Raptors for organizing Port Mortem 2026!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>hackathon</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
