In her QCon San Francisco 2025 presentation, Ruth Linehan, a senior engineer at Momento, challenges two expectations her team had when it began moving caching services from Kotlin to Rust: Rust would make the services faster, but development would cost five times as much or more. After more than three years, her account is more nuanced. The first rewrite did not reduce latency automatically, while Linehan says Rust's compile-time checks made production development less costly than the team had feared.
These notes report Linehan's examples and claims first. The final section is explicitly my synthesis rather than a claim made directly in the presentation.
Context: What Momento Was Optimizing
Momento builds high-performance caching and pub/sub services. Linehan describes daily tests at roughly 60,000 transactions per second with 3 ms p99.9 latency on what she calls "decently large" instances, plus tests that push larger instances to 600,000 transactions per second or beyond. Those figures describe Momento's workloads and test environment; they are not general Rust benchmarks.
The migration proceeded in stages:
- Momento initially wrote its services in Kotlin.
- It moved the most performance-sensitive services to Rust first.
- It later moved its most complex service, a workflow server, even though that service was not performance-sensitive.
For the workflow server, the argument was no longer merely speed. The team already knew Rust, the rest of the services used it, and Linehan says Rust let them express complex behavior more clearly and with greater certainty. She is also careful not to recommend indiscriminate rewrites: Rust is worth considering when a rewrite already has other justification or for a greenfield production system.
The Developer Feedback Loop
Linehan frames productivity as feedback latency: how long after changing code does it take to know whether the change works, and how long after release does it take to discover a defect? Manual deployment and testing lengthen this loop and introduce context switching. Production discovery lengthens it further and raises the cost of diagnosis and impact. As supporting evidence, she cites a 2022 internal Google survey in which 85% of respondents were confident that their Rust code was correct. That is a reported confidence measure, not a measured defect rate or a Momento result.
Her argument is that Rust moves several classes of failure into the compiler, making incorrect code uncompilable rather than merely testable. That can offset the slower first steps of learning the language.
Ownership Makes Data Movement Explicit
In her opening example, a String is passed to a function that takes ownership and then used again for logging. The compiler reports a borrow of a moved value, identifies the ownership transfer, and suggests either borrowing the string or cloning it. Rust's extended error explanation then provides the ownership model and examples.
The immediate experience can feel disproportionate: a logging statement produces a large error. The longer-term value is that the programmer must decide whether the callee should own, borrow, or copy the data. The compiler does not silently choose semantics that may become surprising later.
Linehan contrasts this with a Ruby shallow-copy bug she encountered. Cloning an outer hash left a nested hash shared, so mutating it through one outer hash changed the other. The equivalent arrangement cannot be mutated that way in safe Rust: once the nested map is behind an immutable reference, the compiler rejects the mutation. To make independently mutable copies, the programmer must represent that intent explicitly.
Borrowing Rules Also Constrain Concurrency
Safe Rust permits either multiple immutable references or one mutable reference to a value, but not both at once. Consequently, two threads cannot receive ordinary mutable references to the same map and race to update it.
This guarantee is specifically about safe Rust. Linehan notes that interoperability can require explicitly marked unsafe operations that the compiler cannot verify. unsafe does not make data races valid: it transfers responsibility for upholding Rust's safety invariants to the programmer, and a data race is still undefined behavior.
Shared mutable state still exists, but synchronization becomes part of its type. A Rust Mutex<T> contains the protected value. Code must lock it to obtain a MutexGuard, which provides exclusive mutable access and releases the lock when the guard is dropped. In Linehan's Kotlin contrast, a lock and the protected data can be separate, so code can accidentally access the data without taking the intended lock. Rust makes that path harder to represent.
Lifetimes Describe Which Borrow Survives
Most lifetimes are inferred. Linehan's explicit-lifetime example is a function that returns either a string borrowed from a map or another borrowed string. Because several input references could be the source of the output, the compiler cannot determine the relationship by itself.
The annotation does not extend any value's lifetime. It tells the compiler which input lifetime constrains the returned reference. Her practical lesson is to reason about the source of returned borrowed data rather than react to "missing lifetime specifier" by removing all references.
Types Can Eliminate Domain Mix-Ups
A conventional typed record still permits an account_id and user_id to be swapped if both are plain strings. Linehan describes a bug of this kind that she shipped. Momento uses a macro implementing the newtype pattern to generate distinct wrappers such as AccountId, UserId, and AccountName.
After that change, swapping IDs is a type error. The simplified macro in her example deliberately does not implement Display, forcing callers to decide whether to borrow or clone the inner string. The newtype idea is not unique to Rust, but Rust macros can remove much of its repetitive implementation work.
Rust Can Still Be Slow
Momento's first performance-sensitive Kotlin-to-Rust rewrite was not initially faster in latency. Linehan's point is direct: removing garbage collection and gaining lower-level primitives does not replace sound architecture, measurement, or attention to hot paths.
One production example involved constructing metric names with format! several times per request. Each call created a new String and therefore a heap allocation in a path handling hundreds of thousands of requests per second. Replacing that work with explicit static metric names repeated some source code but, in Momento's load test, improved p99.9 latency by 1 ms. This is a workload-specific result, not a general estimate for replacing format!.
The example also shows why "don't repeat yourself" is not absolute. In a measured hot path, avoiding repeated runtime work can matter more than avoiding repeated source text.
Two Performance Feedback Tools
Criterion for Competing Implementations
Linehan uses the Criterion crate to compare borrowing an endpoint string with cloning it. Criterion repeatedly samples both implementations and produces command-line statistics and an HTML report. In her demonstration, borrowing was faster and the clone had a longer timing tail, which she attributes to the allocation and its greater interaction with the operating system.
The larger lesson is not that every clone is unacceptable. A clone may be required by ownership or API design, and a tiny benchmark may not represent the full service. Criterion is useful when several valid implementations exist and intuition needs a quick empirical check.
Flamegraphs for Finding Where CPU Time Goes
A flamegraph aggregates sampled call stacks. Stack depth appears vertically; box width represents the proportion of samples containing a function. Horizontal position is not elapsed time, and a wide box alone does not reveal whether a function ran often, ran slowly, or both.
Linehan demonstrates this with an Axum/Tokio key-value HTTP server backed by a deliberately named SleepDb. Its write path sleeps for 10 ms while holding a mutex, while the read path must acquire the same mutex. In the read-path flamegraph, little width belongs to the map lookup and much more belongs to waiting for the lock.
In her description of the Linux implementation, a thread waiting on the mutex spins briefly before involving the kernel and being parked through futex machinery. She argues that this coordination means even apparently small lock contention deserves investigation. The example is intentionally artificial, but it exposes a real diagnostic pattern: investigate the measured wait rather than assuming the visibly interesting application function is the bottleneck.
At Momento, the broader loop combines rpc-perf load generation, client and server metrics, system metrics, flamegraphs, and tools such as htop. The team varies one or more factors, such as Tokio worker-thread counts, measures the result, and repeats. Linehan emphasizes that people are poor at guessing performance and that compiler optimizations can make outcomes non-obvious.
Throughput Changed the Migration Story
Although latency did not improve in the initial rewrite, Linehan reports that a single instance went from sustaining 20,000 requests per second to 100,000 requests per second at the same latency. Her lesson is that performance must include throughput and resource use, not only request latency. For a startup paying for cloud instances, serving more requests with the same latency can translate into fewer resources.
This result should be read as Momento's observed migration result, not as evidence that Rust universally produces a fivefold throughput gain. The talk does not provide enough workload, instance, Kotlin/JVM tuning, or implementation detail to reproduce or generalize the comparison.
Adoption Details from the Q&A
The Q&A adds useful constraints to the success story:
- Compile time required engineering. Linehan says large dependencies can make builds slow, but Rust compilation did not feel worse to her than Momento's Kotlin compile-and-lint cycle. The team spent about one engineer-day adding CI caching for dependencies and compiled artifacts.
- Prior experience mattered. Most engineers were not Rust developers when they started, while one teammate had more experience and could establish initial patterns. Linehan believes the migration would have been harder without that person; elsewhere in the Q&A, she characterizes the team's initial experience as mostly experimentation rather than deep expertise.
- They started with a low-risk tool. Before the async services, the team wrote an internal operator CLI in Rust. It provided practice where panic behavior and service performance were not critical.
- Async and Tokio tuning remained difficult. Once initial patterns were established, routine development became easier, but runtime tuning still required workload-specific experiments.
- Observability remained familiar. The team ported equivalent logs and internal metrics and invested in a local development experience and IDE guidance rather than replacing its entire debugging model.
- The toolchain stayed current, with some churn. Momento's CI followed the current Rust release rather than pinning a version. Built-in tooling such as Clippy simplified lint setup, but new releases could break a pull request or require a quick compatibility update and
rustup updatelocally. - The business case was capacity and cost. The decision considered latency SLOs, higher throughput with fewer AWS instances, and whether further Kotlin optimization would delay an eventual move rather than avoid it.
Synthesis: Safety and Speed Share the Same Loop
The following are my takeaways from the presentation rather than direct claims by Linehan.
First, the most compelling migration metric is not language-level benchmark speed. It is validated production capacity per unit of engineering and infrastructure cost. That framing includes latency, throughput, instance count, defect discovery, build time, and the effort needed to operate the service.
Second, compile-time safety and runtime performance are connected indirectly. The borrow checker does not optimize a contended mutex, but stronger invariants can make performance experiments safer to conduct. If a refactor preserves the type and ownership constraints, the team has a smaller set of regressions to investigate while iterating.
Third, migration risk can be staged. Momento's sequence suggests a practical progression: learn through a low-risk internal tool, retain at least one experienced guide, move a service with a concrete business constraint, preserve comparable observability, and measure the whole system. That is more defensible than either "rewrite everything in Rust" or "the learning curve makes Rust too expensive."
Finally, every striking number needs its boundary. The 1 ms tail-latency improvement and the move from 20,000 to 100,000 requests per second are valuable evidence about Momento's systems, but neither is a transferable promise. The durable lesson is the method: make ownership explicit, use types to rule out invalid states, profile the actual workload, and close the feedback loop with repeatable measurements.
Canonical source: Ruth Linehan, "The Rust High Performance Talk You Did Not Expect", QCon San Francisco 2025, published by InfoQ on July 16, 2026.