Resurrecting TinyExpr in Rust: What a Hackathon Taught Me About Proving a Port Works
What if “modernizing” legacy software didn't mean rewriting it and hoping nothing breaks? What if we could take a small, battle-tested C library, rebuild it in a modern language, and prove that the new implementation still behaves like the original? That was the challenge I took on during Code Resurrection 2026. Our project: TinyExpr → tinyexpr-rs, a safe, idiomatic Rust port of the original…
Resurrecting TinyExpr in Rust: Engineering a Behavior-Preserving Port
During Code Resurrection 2026, I embarked on the challenge of porting the TinyExpr mathematical expression parser and evaluator from C to Rust. The project aimed to prove that a modern language implementation could preserve the original's behavior while adopting Rust's modern principles.
TinyExpr, a lightweight C library, parsed and evaluated mathematical expressions like 2 + 3 * 4. The original implementation utilized manual memory management and raw pointers, while the new Rust port sought to leverage Rust's ownership system, pattern matching, Result-based error handling, and strongly typed Abstract Syntax Tree (AST) structures for a safer and more idiomatic implementation.
The core pipeline consisted of Expression → Lexer → Parser → AST → Optimizer → Evaluator → Result. By separating these components, the implementation became more testable and easier to reason about. The lexer converted input into tokens, the recursive-descent parser transformed tokens into an AST, the optimizer performed constant folding, and the evaluator computed the final value.
To ensure behavioral equivalence between the Rust and C implementations, a comprehensive test suite was developed. This suite covered arithmetic operator precedence, associativity, unary operators, variables, mathematical functions, invalid expressions, NaN behavior, infinity behavior, combinatorics, and optimization. Despite initial failures due to precision differences and unsupported features, the tests were adjusted to use appropriate numerical tolerance and document limitations.
In essence, the port wasn't a simple translation from C to Rust syntax. It required a redesign around Rust's strengths, such as ownership, references, and automatic memory management. This approach resulted in a more robust and maintainable implementation while preserving the original's intended behavior.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — it may contain errors, so check the original before relying on it.