The Challenge: Modernizing Without Breaking What Works
The conventional wisdom around modernizing legacy software often implies a complete rewrite. This approach risks introducing regressions and breaking existing functionality. What if, instead, we could take a small, proven C library, rebuild it in a modern language like Rust, and rigorously prove that the new implementation behaves identically to the original? This was the core challenge undertaken during the Code Resurrection 2026 hackathon, resulting in the creation of tinyexpr-rs, a safe and idiomatic Rust port of the TinyExpr mathematical expression parser and evaluator.
TinyExpr is a compact, battle-tested library designed to parse and evaluate mathematical expressions. Given an input like 2 + 3 * 4, it correctly computes 14. Its capabilities extend beyond basic arithmetic to include functions like sqrt(16), sin(pi / 2), pow(2, 10), and handling variables such as in x*x + y*y.

The Porting Process: Safety and Idiomatic Rust
The goal was not merely to create a functional Rust equivalent but to do so in a way that leverages Rust’s safety features and idiomatic programming style. This involved translating C constructs into their Rust counterparts, ensuring memory safety, and utilizing Rust’s type system to prevent common errors. The original TinyExpr, written in C, relies on manual memory management and pointer arithmetic, which are prone to buffer overflows, null pointer dereferences, and other memory-related vulnerabilities. The Rust port, tinyexpr-rs, aims to eliminate these risks by design.
The process involved several key steps:
- Code Translation: Carefully mapping C data structures and control flow to Rust equivalents. This included translating pointer-based operations to Rust’s ownership and borrowing system.
- Error Handling: Replacing C’s error codes or undefined behavior with Rust’s
ResultandOptiontypes for robust error management. - Type Safety: Utilizing Rust’s strong type system to enforce constraints and prevent invalid states, which is inherently more difficult in C.
- Idiomatic Practices: Adopting Rust’s common patterns, such as iterators, closures, and pattern matching, to make the code more readable and maintainable for Rust developers.
The surprising revelation from the hackathon was that the technical implementation of the parser itself was not the most arduous part. The real hurdle was establishing confidence that the new Rust implementation performed precisely as the original C version did, without any deviation in output or behavior.
The Verification Challenge: Proving Equivalence
Software modernization efforts often fall short because the focus is on feature parity or performance gains, overlooking the subtle, yet critical, behavioral nuances of the original. For a library like TinyExpr, which is likely embedded in various systems where its exact output for specific inputs is relied upon, any deviation, however minor, can be catastrophic. The challenge then becomes not just porting the code, but proving its behavioral equivalence to the original.
This verification process is akin to ensuring that a meticulously crafted replica of an antique clock not only looks identical but also keeps time with the exact same precision and quirks as the original. Any difference, whether in the tick-tock rhythm or the way the hands move, means it’s not a true resurrection.
To address this, the team implemented a comprehensive test suite. This involved:
- Unit Tests: Covering individual functions and components of the parser.
- Integration Tests: Testing the complete parsing and evaluation pipeline.
- Fuzzing: Generating a wide range of random inputs to uncover edge cases and unexpected behaviors.
- Comparison Suite: The most critical element was a suite of tests designed to directly compare the output of the original C TinyExpr against the new Rust
tinyexpr-rsfor a vast array of inputs. This required running both implementations side-by-side, feeding them the same expressions, and asserting that the results were numerically identical.
The surprise was how many subtle differences emerged. Even seemingly straightforward expressions could yield slightly different results due to floating-point precision variations, order of operations interpretations in complex nested functions, or differing handling of edge cases like division by zero or overflow conditions. These differences, while perhaps negligible in some contexts, are precisely what make proving equivalence so difficult and essential.
Lessons Learned: Beyond Code Translation
The hackathon project underscored several key takeaways for anyone considering modernizing legacy software:
- Verification is Paramount: The effort spent on proving equivalence far outweighed the effort of the initial code port. For critical libraries, robust testing and verification strategies are not optional extras but core requirements.
- Behavioral Drift is Real: Even simple libraries can have complex, undocumented behaviors that are hard to capture. Modernization must account for these subtle differences.
- Language Safety Benefits: Rust’s memory safety guarantees significantly reduce the risk of introducing new bugs during the porting process, allowing developers to focus more on functional equivalence.
- Hackathons as Proving Grounds: Time-boxed events like hackathons are excellent environments for rapidly prototyping and validating complex ideas, especially those involving risk mitigation and proof-of-concept development.
The successful porting and verification of TinyExpr to Rust demonstrate that modernizing legacy software doesn't have to mean discarding its valuable, battle-tested logic. By prioritizing rigorous verification alongside idiomatic implementation in a safe language, developers can achieve the benefits of modern tooling without sacrificing the reliability of proven code.
