Rewriting a working application in a new language is one of the most expensive decisions an engineering team can make, and one of the most frequently made for the wrong reasons. We modernize .NET systems for a living, so when the question came up for one of our own products, a petabyte-scale LTO tape archive application written in .NET 9, we did the unglamorous thing and actually ran the experiment. We ported the core of it to Rust: a four-crate workspace covering the catalog, the tape read and write engine, a command-line tool, and a desktop shell.
The interesting finding was not "Rust is faster." It is that the rewrite question is almost never about which language is better. Here is the honest scorecard from doing it for real: where Rust earned its keep, where it hurt, and the four questions that actually decide whether you should follow us.
Where Rust Earned Its Keep
Several of the wins were immediate and structural, not matters of taste.
The pipeline that was hardest in C# became the easiest in Rust. Our archive writer streams a file into a TAR container while computing a SHA-256 checksum in the same pass. In .NET that means nesting a TarOutputStream around a CryptoStream around a FileStream, and the disposal order matters: get it wrong and you get truncated writes on the error path. In Rust the same thing is a small generic wrapper, a "hashing writer" built on the standard Write trait, and the language's cleanup guarantees mean the flush happens correctly even when something fails halfway through. The Rust version was shorter than the C# version and harder to misuse. That is the good kind of win.
Reading our legacy data took zero glue code. The old catalog ships as JSON with PascalCase keys and a scattering of missing and nullable fields. One attribute on the Rust type handled the casing for every field at once, and a one-line default handled the missing keys. The C# equivalent is a per-field annotation marathon. We imported years of legacy archives without writing a single line of mapping code.
Dependency management is simply better. A Cargo workspace pins every crate version in one place, and each member project opts in with a single line. After living with NuGet's per-project package references plus a central props file to keep them aligned, this felt like an obvious upgrade. Adding a whole new crate to the workspace was four lines.
Whole categories of bug became visible at compile time. An unknown status code coming out of the database is a case the compiler forces you to handle, instead of silently mapping to zero the way a C# enum does. Error handling that would have been try/catch noise collapsed into clean, linear code. None of this is exotic Rust. It is the boring, everyday safety that adds up over a codebase.
And it is less code. The Rust port reproduces the same functionality in roughly a third to a half of the lines, partly because Rust collapses the plain-object, data-transfer-object, and validator triad that a .NET codebase spreads across three files into one struct with a couple of derived traits.
The Costs Nobody Puts in the Estimate
None of that makes the rewrite free. The costs that sink these projects are rarely the ones in the proposal.
There is no "just install the SDK." .NET's single-installer setup is an underrated superpower. Standing up the Rust toolchain on a locked-down Windows machine cost us 40 minutes fighting antivirus file-locks, a manual toolchain extraction, and hunting down a C linker the standalone download did not bundle. On one developer's laptop that is a one-time annoyance. Across a team and a continuous-integration pipeline, it is real recurring friction you have to own with a documented bootstrap script.
Some constraints are structural, not stylistic. Our SQLite handle is safe to move between threads but not to share across them, so the moment it sat behind a UI boundary we had to wrap it in a lock. That is the correct design for a single-writer catalog, but it is the kind of thing you only learn by hitting it, and a team new to the language will hit a dozen such walls before the patterns become second nature.
The biggest cost is not the language at all. The engine port went quickly. What does not port quickly is the years of productized ergonomics wrapped around it: the desktop chrome, the settings and credential storage, the installer, the code signing, the auto-update channel, the offline runtime bootstrapping for customers who are not online. Our desktop shell compiled and opened a real window in 25 minutes. Getting from "a window opens" to "a non-technical customer can install, trust, and update this" is the multi-week mountain. The language was never the hard part. The product around the language was.
So we sized it honestly: roughly 9 to 14 weeks for one competent-but-not-expert engineer to reach feature parity with the .NET app. A Rust veteran who has shipped a desktop application before would do it in 6 to 8. Either way, that is real time you are spending instead of shipping features your customers asked for.
The Four Questions That Actually Decide It
Notice that nothing above is "which language is better." A rewrite is a business decision wearing an engineering costume. Before you start one, answer these in order.
- What specific problem does the current codebase have? "I want to learn Rust" is a fine reason to start a side project and a terrible reason to rewrite a product. If you cannot name the concrete pain (a class of bug, a performance floor you keep hitting, a deployment story you cannot stand behind), stop here. You are about to spend three months solving a problem you have not defined.
- Is the rewrite cheaper than the friction it removes? Our number was 9 to 14 weeks. If the friction you are escaping is structural and would otherwise persist for a decade, that trade can pencil out. If it is accidental complexity that one focused refactor in C# would fix, it does not. Most of the time, it does not.
- How strong is your team in the new language? One expert paired with someone learning produces good Rust. Three beginners produce code that is worse than the equivalent .NET team would have written, because the rate of subtle mistakes per week is higher while everyone is still climbing the curve. Be honest about which team you actually have.
- What does the customer notice? A rewrite done under a feature freeze looks, from the outside, like the product was broken for six months and then magically became the same. That is the worst possible experience for someone paying you. If you rewrite, ship the new version before you retire the old one. Never freeze your customers in place while you rebuild.
So When Should You Actually Do It?
Rust earned the bet for us on this one product because the answers lined up. The friction was structural: a single self-contained binary and compile-time memory safety genuinely matter when you are writing irreplaceable archives to an 18-terabyte tape that no one will read back for a decade. The domain let the type system delete whole classes of bug. And we had the Rust depth to carry it. When those things are true, Rust is not hype. It is leverage that compounds for years.
But for most of the .NET systems we are brought in to assess, the answers do not line up. The pain is accidental complexity a refactor away. The team is all-.NET. The application is interface-heavy, where the chrome is the bulk of the value and the language underneath it is almost incidental. In those cases the right move is not a rewrite at all. It is modernizing the .NET you already have: the current runtime, ahead-of-time compilation, trimming, and a cleanup of the actual hotspots. That gets you most of the benefit for a fraction of the cost and none of the customer-facing freeze.
The language comparison is the easy, fun part of this conversation, which is exactly why it gets all the attention. The decision underneath it is harder and more boring: what does your codebase actually cost you today, and is twelve weeks the cheapest way to fix it? Answer that first. The language almost always sorts itself out once you have.
