Programming and Software Development

How to Gradually Integrate Rust to Speed Up an Existing Codebase Without Rewriting It Entirely

Lily Mara explains a methodology for restructuring functions through the Foreign Function Interface (FFI) to move specific parts of Python to Rust using PyO3, while preserving the existing application and reducing the risks of a wholesale rewrite. The experiment shows that function-level speedups can exceed 100 times, while the improvement in the overall HTTP test reached only about 15%, highlighting the importance of system-level measurement.

2026-09-15
5 min read
10 views
فريق تحرير certi.news
How to Gradually Integrate Rust to Speed Up an Existing Codebase Without Rewriting It Entirely

Rather than rewriting an entire application in Rust, Lily Mara, a Staff Engineer at Discord, proposes gradually moving the most resource-intensive functions from a dynamic language such as Python to Rust and linking the two implementations within the same process. Mara presented this methodology in a session published through InfoQ, drawing on her experience building distributed systems to send tens of billions of notifications daily to Discord users, as well as on her book Refactoring to Rust.

The core idea is not to replace one language with another simply because Rust is faster, but to identify narrow parts of the system where optimization will have the greatest impact. This approach reduces the scope of change, enables the new behavior to be tested against the old, and preserves the original implementation in cases where guaranteeing identical results is difficult.

Why not start with a complete rewrite?

Mara believes that the enthusiasm for rebuilding an entire legacy system in a newer language is understandable, but carries significant practical risks. Rewrite projects can miss deadlines, become more complex than expected, or reintroduce bugs that the old system had already addressed. Legacy code is not always complex because of its age; it may reflect constraints and details accumulated through real-world use and institutional knowledge that are difficult to transfer to a new project.

The session also warns against reducing a performance problem to the programming language alone. The cause of slowness may lie in the database schema, query and caching patterns, or service design, rather than in the cost of executing a line of code. Therefore, benefiting from Rust does not mean that a rewrite will automatically address architectural bottlenecks.

What does FFI refactoring mean?

The methodology uses what Mara calls FFI refactoring: rewriting a function or a small part of the functionality in another language and connecting it to the existing application through a Foreign Function Interface. In the example presented, the Flask application continues to receive the HTTP request and decode the JSON, then sends the data to a statistics function written in Rust, before returning the results to Python and sending the response.

The connection relies on the C interface, which effectively serves as a common language among many systems and languages. In the case of Python, the PyO3 project provides tools for creating a module that can be imported from Python, while Maturin can be used to build that module. Programming attributes such as pyfunction and pyclass turn functions and data structures in Rust into interfaces that can be called from Python.

Choosing the right function and measuring the impact

The experiment recommends looking for functions that combine frequent invocation with a high invocation cost. A function may be relatively inexpensive each time, but run on every request, such as validation logic located in front of API handlers. Conversely, some operations may be rare but extremely expensive. The criterion is the cumulative impact on processor time, not the impression that a particular function appears slow.

In the statistical example, the Rust implementation was slightly more than one hundred times faster when the function itself was measured through Python, even though the test ran both applications through the Python interpreter. But when a complete HTTP handler was measured, including Flask, JSON serialization and deserialization, the improvement was only about 15%. The original function took approximately 86 microseconds to execute, a small amount of time in isolation, but one that can become a significant cost when repeated at scale.

This gap between partial and overall measurements is one of the session's most important lessons: recording the function's speedup is not enough. Overall tests must represent the actual usage path and include the network or web framework and data serialization when necessary.

Functional compatibility, testing, and limitations

Reimplementing the example revealed a difference between the results of the statistics library in Python and the Rust library. One of the libraries calculated quartiles precisely, while the other used estimates suitable for very large datasets; slight differences in decimal rounding also appeared. Therefore, replacing a library should not be assumed to preserve the same behavior automatically.

If exact equivalence is required, it is possible to look for another library, reimplement the original algorithm in Rust, or keep the sensitive part in Python. The benefit of function-level migration is the ability to combine the two languages within the same process, instead of separating the component into an independent service and adding network communication and new operational costs.

As for testing, it includes direct Rust tests, the existing Python tests for Flask handlers, tests comparing the results of both implementations, and property-based testing when appropriate. However, adding native code introduces operational complexities: development environments need a Rust compiler or binaries compatible with the operating system and architecture, deployment becomes more complex, and new errors may appear.

In practice, this methodology offers a relatively low-risk path for improving selected parts of existing systems, but it does not eliminate the need for architectural analysis, compatibility testing, and overall measurements. Real savings are demonstrated only when the improvement is reflected in the complete service path, not in an isolated function benchmark.

News source
InfoQ - Architecture Articles
Open original source ↗
ف
Author

فريق تحرير certi.news

In the same category

You may also like

View all news