DEV Community

Sri Varshan
Sri Varshan

Posted on

Port Mortem 2026: Engineering a Behaviorally Equivalent Rust Port of python-semanticversion

πŸš€ Port Mortem 2026: Rebuilding python-semanticversion in Rust β€” Proving a Port Is More Than Just a Rewrite

β€œThe hardest part of portingsoftware isn’t writing new code. It’s earning the confidence that you didn’t change its behavior.”

Every software engineer has seen version numbers like:

1.2.3
2.0.0-beta
3.4.1+build.12

They look harmless.

Behind those few characters lies one of the most important contracts in modern software engineering.

Package managers.
CI/CD pipelines.
Container registries.
Dependency resolvers.

They all depend on one thing:

Correctly understanding Semantic Versioning.

A single mistake in version precedence can install the wrong dependency.
A single parsing bug can break an automated deployment.
A single compatibility mistake can affect thousands of downstream projects.

That realization completely changed how I viewed this project.

βΈ»

🎯 The Challenge

For Port Mortem 2026 (Track D – Python β†’ Rust), I chose to port the mature Python library python-semanticversion into Rust.

The goal wasn’t:

❌ Write a faster library.

❌ Design a new API.

❌ Add extra features.

Instead, the goal was significantly harder:

Can I rebuild the same library in another language while preserving every observable behavior?

That sentence became the guiding principle for the entire project.

βΈ»

πŸ€” Why This Project?

Most hackathons naturally lean toward visible products:

  • AI assistants
  • SaaS platforms
  • Mobile apps
  • Dashboards

Those projects are exciting because you can immediately see the result.

I wanted to explore a different category of software.

Developer infrastructure.

Infrastructure projects rarely have flashy user interfaces.

Instead, they quietly power everything around them.

If they work correctly, nobody notices.

If they fail, everyone notices.

That invisible reliability is what attracted me to this challenge.

βΈ»

πŸ¦€ Why Rust?

Choosing Rust wasn’t simply about performance.

Rust offered something equally important:

  • Strong compile-time guarantees.
  • Memory safety without a garbage collector.
  • Predictable execution.
  • Excellent tooling.
  • A growing ecosystem for systems programming.

More importantly,

Rust forces you to think carefully about ownership, correctness, and explicit design.

That mindset aligned perfectly with the objective of building a trustworthy port.

βΈ»

πŸ—οΈ High-Level Architecture

(Insert a clean architecture diagram here)

             Original Python Library
             python-semanticversion
                     β”‚
                     β”‚
     Read Source β€’ Understand Semantics
                     β”‚
                     β–Ό
         Rust Implementation (semver-rs)
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚                                        β”‚
 β”‚  Version Module                        β”‚
 β”‚  SimpleSpec Module                     β”‚
 β”‚  NpmSpec Module                        β”‚
 β”‚  PyO3 Python Bindings                  β”‚
 β”‚                                        β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
         Validation & Compatibility Tests
                     β”‚
                     β–Ό
         Behavior Matches the Original
Enter fullscreen mode Exit fullscreen mode

The architecture deliberately separates implementation from validation.

Writing code was only half the work.

Proving the implementation behaved like the original was equally important.

βΈ»

🧠 A Different Definition of Success

When building a new application, success usually means adding features.

When porting a mature library,

success means not changing anything users depend on.

That completely changes how you think.

Every parser.

Every comparison.

Every edge case.

Every public API.

They all become contracts.

Instead of asking,

β€œDoes my code work?”

the question becomes,

β€œDoes my code behave exactly like the original implementation?”

That distinction shaped every engineering ****decision throughout this project.

βΈ»

πŸ“– Understanding Before Implementing

One assumption disappeared almost immediately.

I originally believed porting would mostly involve translating Python syntax into Rust.

It didn’t.

The real work was understanding why the Python implementation behaved the way it did.

Before writing Rust, I spent time reading the original source code, identifying design patterns, understanding comparison rules, and tracing how different modules interacted.

Only after understanding the intent behind the implementation did I begin writing Rust.

That single decision probably saved more time than any optimization later in the project.

βΈ»

βš™οΈ Engineering Philosophy

Throughout development I followed one simple principle:

Behavior first. Implementation second. Optimization third.

That meant every module followed the same workflow.

Read Original Source
β”‚
β–Ό
Understand Existing Behavior
β”‚
β–Ό
Design Rust Structures
β”‚
β–Ό
Implement
β”‚
β–Ό
Test
β”‚
β–Ό
Document
β”‚
β–Ό
Commit

Small, incremental milestones made it much easier to reason about correctness than attempting a complete rewrite all at once.

βΈ»

πŸ“Œ Repository Overview

(Insert GitHub Repository Screenshot Here)

Project layout:

semver-rs
β”‚
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ version.rs
β”‚ β”œβ”€β”€ spec.rs
β”‚ β”œβ”€β”€ npm_spec.rs
β”‚ └── lib.rs
β”‚
β”œβ”€β”€ tests/
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ README.md
β”œβ”€β”€ DECISIONS.md
└── THIRD_PARTY_LICENSE

Each component was intentionally kept modular.

That separation made both implementation and validation significantly easier as the project evolved.

βΈ»

➑️ In Part 2, I’ll dive into the implementation strategy, behavioral equivalence, testing methodology, engineering decisions, and the lessons that mattered most during the port.

From Translation to Engineering

One of the biggest misconceptions I had before starting this project was that porting software is mostly about translating syntax.

In reality, syntax is the easiest part.

The real challenge is understanding the behavioral contract of the original library. Every public method, every comparison rule, every parser, and every edge case represents an expectation that existing users rely on. Breaking even one of those expectations means the port is no longer a true replacement.

That realization changed my workflow completely.

Instead of writing Rust immediately, I spent time understanding the original implementation first. I treated the Python library as the reference specification and used it to guide every implementation decision.

βΈ»

Implementation Strategy

Rather than attempting a complete rewrite in one pass, I divided the project into independent milestones.

Reference Python Source
β”‚
β–Ό
Study Existing Behavior
β”‚
β–Ό
Design Rust Data Structures
β”‚
β–Ό
Implement One Module
β”‚
β–Ό
Validate with Tests
β”‚
β–Ό
Document Decisions
β”‚
β–Ό
Commit & Push

This incremental approach reduced risk and made debugging significantly easier. Every completed module became a stable foundation for the next.

βΈ»

Core Components

The implementation was organized into independent modules, each responsible for a specific part of the Semantic Versioning specification.

Module Responsibility
version.rs Parse Semantic Versions and implement comparison logic
spec.rs Evaluate version constraints such as >=1.0.0,<2.0.0
npm_spec.rs Support npm-style version ranges and compatibility rules
lib.rs Expose a clean public API
tests/ Validate expected behavior
DECISIONS.md Record important engineering decisions

This separation of concerns kept the codebase easier to reason about and maintain.

βΈ»

Behavioral Equivalence

The central question throughout development was simple:

How do I know the Rust implementation behaves like the Python implementation?

Compiling successfully wasn’t enough.

Instead, every feature was validated against the behavior of the reference implementation.

The workflow looked like this:

Input
β”‚
β–Ό
Python Implementation
β”‚
β–Ό
Expected Output
β”‚
β–Ό
Rust Implementation
β”‚
β–Ό
Compare Results

If both implementations produced the same observable behavior, the feature was considered complete.

This mindset changed the goal from β€œmake the code work” to β€œmake the implementation trustworthy.”

βΈ»

Engineering Decisions

Several technical decisions influenced the final implementation.

  1. Behavior before optimization

Performance improvements are valuable, but correctness comes first. Every implementation choice was made with compatibility as the highest priority.

βΈ»

  1. Incremental commits

Rather than producing one large commit at the end, the project evolved through small, focused commits.

This provided:

  • Clear development history
  • Easier debugging
  • Better documentation of progress


βΈ»

  1. Documentation as part of engineering

Engineering doesn’t end when the code compiles.

Throughout development, architectural decisions and implementation reasoning were recorded in DECISIONS.md.

Documenting why a decision was made is often just as valuable as documenting what was built.

βΈ»

Testing Strategy

Testing became the foundation of confidence throughout the project.

Every completed feature followed the same cycle:

Implement
β”‚
β–Ό
Compile
β”‚
β–Ό
Run Tests
β”‚
β–Ό
Fix Issues
β”‚
β–Ό
Verify Again
β”‚
β–Ό
Commit

This continuous validation reduced regression risk and ensured each completed milestone remained stable before introducing additional functionality.

βΈ»

What This Project Changed for Me

This project changed how I think about software engineering.

Previously, I viewed programming primarily as the act of writing code.

After working on this port, I realized that engineering begins much earlier.

It starts with understanding existing systems, respecting established behavior, validating assumptions, documenting decisions, and only then implementing a solution.

The amount of code written became less important than the confidence I had in its correctness.

βΈ»

Lessons That Will Stay With Me

Several lessons from this project will influence how I approach future software engineering work.

  • Read the existing system before attempting to improve it.
  • Small, incremental progress scales better than large rewrites.
  • Tests build confidenceβ€”not just correctness.
  • Documentation is part of the product.
  • A successful port preserves behavior before pursuing optimization.

One principle summarizes the entire experience:

β€œSoftware engineering isn’t measured by how much code we write, but by how confidently others can rely on it.”


Acknowledgement

This write-up was created as part of Port Mortem 2026.

Thank you to Hackathon Raptors for organizing a challenge that encouraged participants to document not only the final implementation but also the engineering journey behind it.

PortMortem2026 #HackathonRaptors

Top comments (0)