Abdulkader Safi

Microsoft ported Copilot to Rust. Why not C# or Go?

GitHub moved the Copilot runtime from TypeScript to 832,000 lines of Rust with agents, for about $120,000 in tokens. Here is why C# and Go lost, and what the benchmarks show.

12 min read

Share
GitHub Copilot runtime migrating from TypeScript to Rust, with C# and Go shown as the options Microsoft passed on

Microsoft owns C#. It has spent 25 years and billions of dollars on .NET. And when it rebuilt the engine behind GitHub Copilot this year, it picked Rust.

Last year it did the same thing to the TypeScript compiler, except that time the winner was Go. Two of Microsoft's most visible rewrites in two years, and neither one went to its own language.

So .NET developers are asking a fair question. Has Microsoft given up on C#?

Short answer: no. The longer answer is more useful, because the reason C# and Go lost this particular job tells you how to pick a language for your own rewrite. I read the whole write-up (it is a 65 minute read) and watched the reaction, so here is the version with the numbers left in.

What GitHub actually rebuilt

The Copilot runtime is the agent loop. It takes your prompt, calls the model, runs tools, manages the session and streams events back. It was written in TypeScript on Node.js.

That was fine when it only powered the Copilot CLI. Then it grew. According to the GitHub post by Stephen Toub, published 16 September 2026, the same runtime now sits behind the Copilot CLI, VS Code, Visual Studio, the cloud coding agent, Copilot code review, Copilot Studio, and Excel, Outlook, PowerPoint and Word.

It is also reached through six SDKs: C#, TypeScript, Python, Go, Java and Rust.

Here is where it fell apart. Every time an app created a Copilot client, the SDK spawned the CLI as a separate process. That meant booting Node, starting V8 and parsing a large pile of JavaScript before the first message could go anywhere. Toub puts the cost at "on the order of 100 MB of working set minimum" per client, and every function call had to cross a process boundary. If Node crashed, your session went with it.

A Python app, a Java service and a Word add-in were all paying for a JavaScript engine they never asked for.

The requirements decided the language before anyone argued about it

The team wrote down what the new runtime had to do. Paraphrased from the post:

  1. Split the terminal UI away from the runtime so the runtime is a clean library
  2. Minimal dependencies and minimal overhead
  3. Embed inside the host process, not beside it
  4. Top-tier performance, scalability and reliability
  5. Great interop, so all six SDKs can call it through their own foreign function interface (FFI, the mechanism a language uses to call code compiled in another language)
  6. A toolchain with less supply chain risk

Then this line: "For all those reasons, as well as softer reasons (such as team experience and industry direction), we chose Rust."

Read requirement 3 again. That one does most of the work in everything that follows.

So why not C#?

This is the part people get wrong in both directions.

C# was not rejected for being slow or for being a bad fit for agents. Toub says the opposite in his own post. When he analysed the 8,678 Rust compiler errors the agents hit, 84% were ordinary static typing mistakes: wrong names, missing fields, type mismatches. His words: "a C# or Java or Go compiler would catch all of them just as well, several of them with friendlier diagnostics, and all of them a great deal faster."

Borrow checker errors, the thing Rust is famous for, were 1.7% of the total.

So the "Rust is magic for AI code" story does not hold up, and the person debunking it is the one who did the port. The problem with C# was never the language. It was where the code has to run.

A runtime that loads inside a Python process, a Java process, a Go process and Office has to be a good guest. It should not bring a second garbage collector (the part of a runtime that frees memory automatically, and pauses your program to do it), a second thread pool and a second runtime to boot into somebody else's house.

.NET can technically do this. Native AOT compiles C# ahead of time and can export plain C functions that any language can call. I have seen people use it to write Node addons in C#. But the library still carries its own runtime and GC inside the host, and Microsoft's own docs say Native AOT libraries cannot be unloaded. For a component that ends up inside Word on hundreds of millions of machines, that is a lot of baggage to justify.

Rust has no garbage collector and no runtime to start. You get a C ABI, predictable memory and nothing else. That matches requirement 3 exactly.

I build .NET APIs for a living at dsrpt, and I once moved a slow client dashboard off Laravel onto .NET because C# was the faster tool for that job. I wrote it up in how I made a dashboard 10x faster by moving to .NET and Next.js. A web API that owns its own process is exactly where .NET is great. An embedded library inside six other language runtimes is a different job.

Did Microsoft give up on its own language?

The anger is real. As quoted in this video breakdown, .NET developer Peter Morris wrote: "First TypeScript compiler, now GitHub Copilot has been ported to Rust. I'm getting a massive vibe that Microsoft is not taking C# seriously. If C# isn't good enough, make it good enough."

I get the feeling. I don't agree with the conclusion, for four reasons:

  • The engineer who ran the port is Stephen Toub. He is a Distinguished Engineer at Microsoft and the person who writes the giant yearly "Performance improvements in .NET" posts. If anyone would have pushed for C# where C# fit, it is him.
  • C# is a first-class consumer of the new runtime. The C# SDK loads the Rust core in-process through P/Invoke, same as every other SDK.
  • .NET is still shipping on schedule. .NET 10 landed as an LTS release, and huge chunks of Azure and Microsoft's internal services run on it.
  • "Industry direction" is about C and C++, mostly. Microsoft has been pushing Rust into Windows and Azure for years. In December 2025 Distinguished Engineer Galen Hunt posted that his goal was to "eliminate every line of C and C++ from Microsoft by 2030". Microsoft later clarified that this is a research effort, not a Windows rewrite, but the direction is clear. Rust is replacing the systems-level code. C# is not in that fight.

This is how the C# SDK picks the new in-process runtime, taken from the post:

var client = new CopilotClient(new CopilotClientOptions
{
    Connection = RuntimeConnection.ForInProcess()
});

One line, and a .NET app gets the Rust engine without spawning Node. That's the C# story here.

And why not Go, when Go won the TypeScript compiler?

This is the more interesting question, because on paper the two projects look alike: big TypeScript codebase, needs to be faster, Microsoft picks a compiled language.

The TypeScript team explained their pick in a GitHub discussion in March 2025. Their main reason was shape. The TypeScript compiler is mostly functions and data structures with almost no classes, and idiomatic Go looks a lot like that code, so they could port it file by file and keep the logic the same. Go's garbage collector was a plus: it let them control memory layout without making every line of the compiler think about memory. Rust and C# would both have needed a redesign. I covered the result in TypeScript 7 is written in Go now, and the speedup was around 10x.

Now compare the jobs:

  • The TypeScript compiler is a program. You run it, it owns its process, it exits. A GC is harmless there.
  • The Copilot runtime is a library. It lives inside someone else's process for as long as that app runs.

Go can build a C shared library, but that drags the Go runtime, its scheduler and its garbage collector into the host too. That is the same problem C# has, arguably worse, because Go's runtime was never designed to be a guest.

Same company, same starting language, opposite answers, and both were right. If the difference between a program and a library inside another program is fuzzy for you, my threads vs processes guide covers the basics.

The numbers: 832,000 lines, $120,000, three weeks

Here is what the port cost, all from Toub's post:

  • About 430,000 lines of production TypeScript went through the port
  • The result is 832,378 lines of production Rust plus 468,689 lines of Rust unit tests
  • 128 pull requests, from 12 May to 21 August 2026
  • About $120,000 in tokens, mostly cached reads (the cache hit rate was 96.22%)
  • Roughly three weeks of one developer's time, spread over those months

Toub is careful to say it was not 100% one person. Other engineers built pieces like the temporary interop layer. But the bulk of the code was written by agents, and he estimates the same project "would have taken a whole team of developers a year or two before agents."

The method matters more than the cost. They did not freeze the codebase for a big rewrite. Each pull request replaced one piece of TypeScript with a thin shim calling into Rust and deleted the old code in the same change. Main stayed shippable the whole time, and 174,675 lines of existing end-to-end tests ran against the new Rust at every step.

That test suite is the real hero. Without it, $120,000 of agent output is just a very expensive guess.

Bun did something similar this year, moving 535,000 lines from Zig to Rust with a fleet of Claude agents. We wrote about why "every test passes" isn't the same as "someone understands this code" in Bun's Rust rewrite passed every test. No human read the code. GitHub's post answers part of that worry. It lists dozens of regressions the tests missed, and how each one was found and fixed.

Did it work? The benchmarks

Yes. These runs used a fake local model server, so they measure the runtime itself, not model speed:

Scenario TypeScript (May 12) Rust out-of-process Rust in-process
Client, session, one turn 5.25 s 1.33 s (4.0x) 292 ms (18.0x)
Resume a 32-turn session 5.64 s 1.52 s (3.7x) 264 ms (21.4x)
Ten concurrent client lifecycles 12.34 s 4.18 s (3.0x) 742 ms (16.6x)
1,000 one-turn session lifecycles 132.52 s 22.53 s (5.9x) 20.93 s (6.3x)

Memory is where the requirement 3 decision pays off. During the ten-client test, the old Node process tree peaked at 1,383 MB above baseline. Rust out-of-process peaked at 247 MB. Rust in-process: 126 MB.

On the server-style test, 100 concurrent pipelines each running ten sessions, TypeScript handled 7.55 session lifecycles per second. Rust in-process handled 120.

Toub adds a fair warning: other changes landed in the same period, so this compares the old system with the new one, not one language with another. Rust is not "15.9x faster" at everything. It is much faster at the specific thing a server hosting many Copilot sessions needs.

Nobody read 832,000 lines, and that's the new job

You cannot review 832,000 lines of Rust by hand. They didn't try.

The team wrote a review skill that told agents to compare old TypeScript and new Rust line by line and confirm the behaviour matched. Review bots, schema checks and the E2E suite did the mechanical work. Humans took the rest. In Toub's words: "I chose the destination architecture, decided what behavior mattered, partitioned the work, resolved ambiguous trade-offs, judged the evidence, manually reviewed high-risk areas... and made the final merge decisions."

And the line I'd pin above every engineer's desk: "The agents changed the amount of code one engineer could supervise. They did not remove the need for an engineer who understood the system and could vouch for the direction, the guardrails, and the release."

That matches what I see. Writing code got cheap. Deciding what the code should do, and proving it does it, did not. I made the same argument with numbers in AI made writing code cheap. Review is still expensive. If you are building your own review skills for agents, the markdown-file approach I use is in Claude Skills: 8 markdown files that replaced my npm scripts.

On a much smaller scale I built WP2Code on the same idea: port a WordPress site to another stack and let the machine check the new version against the original, section by section, instead of trusting your eyes. The principle scales. Measure the old thing, measure the new thing, and don't argue with the diff.

What to take from this for your own stack

Forget "Rust good, C# bad." The lessons that carry over:

  1. Pick the language from where the code runs. Own process and long-running: C#, Go and Java are all great. Embedded inside other runtimes, or you need a tiny footprint: that is Rust's home turf.
  2. Your test suite decides whether a rewrite is possible. GitHub could point agents at 430,000 lines because 174,675 lines of end-to-end tests could tell them when something broke. No tests, no port.
  3. Port in place. Shim, replace, delete, ship. Never freeze the codebase.
  4. Budget for the regressions tests can't see. The post lists missing features, a blocked UI thread and flashing console windows on Windows. A compiler can't object to code that isn't there.
  5. Rewrite math has changed. $120,000 and three weeks of senior time is a normal software budget. Ports that were "never worth it" two years ago might be worth pricing now.
  6. Keep a human who can say no. Every good outcome in that post traces back to one engineer who understood the system and made the final call.

If you have a slow, painful service that everyone agrees should be rewritten "someday", this is the week to price it properly. Count your end-to-end tests first. That number tells you whether agents can help you, or whether you'd be paying for a very fast way to break things.

Sources

Last updated 28 Sep 2026 · filed under Rust, ai, dotnet, c#, agents, ai agent, hermes agent

FAQ

Frequently asked questions

Why did GitHub rewrite the Copilot runtime in Rust?

The TypeScript runtime ran on Node.js and every SDK client had to spawn it as a separate process, costing around 100 MB of memory per client and slow startup. GitHub needed a runtime that could be embedded directly inside apps written in C#, Python, Go, Java, TypeScript and Rust, plus Office apps. Rust has no garbage collector or runtime to boot, exposes a plain C interface, and keeps memory use predictable, which fit those requirements.

Why didn't Microsoft use C# for the Copilot rewrite?

C# was not rejected for speed or code quality. The runtime has to live inside other programs' processes, and a C# library brings its own runtime and garbage collector into every host. .NET Native AOT can export C functions, but it still carries that runtime and cannot be unloaded. Stephen Toub, who led the port, even noted that a C# compiler would have caught the agents' typical errors as well as Rust did.

Why did the TypeScript compiler use Go but Copilot use Rust?

They are different kinds of software. The TypeScript compiler is a standalone program that runs and exits, and its code shape (functions and data structures) mapped closely to idiomatic Go, so it could be ported file by file. The Copilot runtime is a library embedded inside other applications, where Go's own runtime and garbage collector would be unwelcome. Different job, different language.

How much did the Copilot Rust migration cost?

About $120,000 in AI tokens plus roughly three weeks of one senior engineer's time, spread between 12 May and 21 August 2026. Most tokens were cheap cached reads, with a 96.22% cache hit rate. The port turned about 430,000 lines of TypeScript into 832,378 lines of production Rust and 468,689 lines of unit tests across 128 pull requests.

Is Microsoft abandoning C# and .NET?

No. The C# SDK is a first-class consumer of the new Rust runtime, .NET 10 shipped as an LTS release, and much of Azure runs on .NET. Microsoft's Rust push mainly targets C and C++ systems code in Windows and Azure. The Copilot runtime went to Rust because it is an embedded library, a job where no garbage-collected language fits well.

Written by

Abdulkader Safi

Senior & Lead Software Engineer

Lead engineer at DSRPT, from Lebanon and based in Kuwait. I write about the tools and bugs from real client work, with the numbers I measured.

About me → GitHub LinkedIn

Need this kind of work done on your project?

Start a project →

Keep reading

All articles →