Build1 publisher3 min readPublished
FFI refactoring draws the Rust boundary around a single Python function
Discord staff engineer Lily Mara argues full rewrites regenerate bugs the old code had already fixed, so she replaces functionality one function at a time across the C ABI. The published transcript makes the case for the method before it shows a number.
The Engineer · Build desk

What happened
- Lily Mara, a staff engineer at Discord, used an InfoQ presentation to argue for making existing software faster by adding Rust gradually instead of restarting the codebase.
- She named the technique FFI refactoring: rewrite one function's worth of functionality in Rust, and connect it to the surviving Python through the C Foreign Function Interface.
- The published transcript stops mid-sentence as she begins the Python-to-Rust example, so it carries no measured speedup and no figure for what crossing the boundary costs.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The win is capped at whatever the ported function was costing, so a team that grafts Rust onto code whose time is really going into queries or network hops buys build complexity and gets nothing back.
- decision Putting the boundary below the service line changes who has to agree: the argument becomes which function to port, not which service to extract, and it stays inside the team that owns that function.
- cost Adoption puts a compiled native artifact into a Python project's build and release path, and the supplied transcript does not price that against the speedup it promises.
- contradiction The talk both invokes Rust's speed over Python, Ruby, and Node and argues language choice is not where the time goes, which leaves the reader to settle the tension with their own profiler before the method means anything.
The load-bearing part of this design is the C ABI, and it is load-bearing for an unglamorous reason. Every mainstream operating system and language already knows how to call a C function, because there is too much working C in the world for any of them to opt out [8]. A Rust function reachable through that interface therefore has a caller in Python without anyone inventing a protocol, and the unit of replacement drops below the service boundary to a single function [6][7].
That granularity is what makes the tradeoff arguable rather than obvious. Mara herself lists what a language swap does not fix: architecture, database schema, querying patterns, caching patterns, and the layers of microservices in the stack [5]. That list is also the sharpest objection to her own method. If the time is going into a query pattern or three hops of network, moving one function into Rust relocates work that was never the bottleneck, and you have added a compiled native artifact to a Python build to get there [11].
So the condition for any speedup to transfer is narrow. The function has to be CPU-bound in the caller's own process, and its cost has to be large next to whatever crossing the boundary costs. The supplied transcript gives neither figure; it breaks off mid-sentence while the Python-to-Rust example is still being set up, so at this stage the demo is a promise [9]. The framing that motivates the whole talk is also a secondhand claim rather than a measurement: Rust is said to be much faster than the Python, Ruby, or Node that monoliths are written in, and that reputation is what produces the urge to start over [10].
What the excerpt does establish is the case against the rewrite, and it rests on experience rather than instrumentation. Rewrites overrun, and they reintroduce bugs the old system had already found and fixed, because the real-world constraints are recorded in the legacy code and often nowhere else [4]. Mara has been using Rust for over a decade and professionally since 2019, and wrote "Refactoring to Rust" on this subject, so the position is a program rather than an anecdote [2][3].
In my context that historical-knowledge argument is the deciding factor, and I would take the function-level graft over a rewrite on it alone. I would still refuse to choose the target function by reading code. The order I would insist on is profile, then port, because Mara's own list of non-language causes [5] is exactly why most plausible-looking candidate functions are the wrong candidate. If a team cannot name the function and show its share of wall-clock time, the C ABI is not the tool they are short of.
What to watch
- Whether the full talk gives a per-call cost for the C boundary and a before-and-after profile of the ported Python function.
- Whether the worked example passes only simple scalars or handles errors and object ownership across the boundary, which decides if the pattern survives real payloads.
- Whether Discord publishes production numbers for a function-level Rust graft rather than a service extraction.