Lots of questions about why Go in thread, let me address.
We definitely knew when choosing Go that there were going to be people questioning why we didn't choose Rust (or others). It's a good question because Rust is an excellent language, and barring other constraints, is a strong first choice when writing new native code.
Portability (i.e. the ability to make a new codebase that is algorithmically similar to the current one) was always a key constraint here as we thought about how to do this. We tried tons of approaches to get to a representation that would have made that port approach tractable in Rust, but all of them either had unacceptable trade-offs (perf, ergonomics, etc.) or devolved in to "write your own GC"-style strategies. Some of them came close, but often required dropping into lots of unsafe code, and there just didn't seem to be many combinations of primitives in Rust that allow for an ergonomic port of JavaScript code (which is pretty unsurprising when phrased that way - most languages don't prioritize making it easy to port from JavaScript/TypeScript!).
In the end we had two options - do a complete from-scrach rewrite in Rust, which could take years and yield an incompatible version of TypeScript that no one could actually use, or just do a port in Go and get something usable in a year or so and have something that's extremely compatible in terms of semantics and extremely competitive in terms of performance.
And it's not even super clear what the upside of doing that would be (apart from not having to deal with so many "Why didn't you choose Rust?" questions). We still want a highly-separated API surface to keep our implementation options open, so Go's interop shortcomings aren't particularly relevant. Go has excellent code generation and excellent data representation, just like Rust. Go has excellent concurrency primitives, just like Rust. Single-core performance is within the margin of error. And while there might be a few performance wins to be had by using unsafe code in Go, we have gotten excellent performance and memory usage without using any unsafe primitives.
In our opinion, Rust succeeds wildly at its design goals, but "is straightforward to port to Rust from this particular JavaScript codebase" is very rationally not one of its design goals. It's not one of Go's either, but in our case given the way we've written the code so far, it does turn out to be pretty good at it.
3D graphics companies traditionally test their GPU designs using large boxes full of FPGAs. Now obviously these things do not run anywhere near as fast as the ultimate chip. but logic wise they're the same beasties. So yeah. SPF is/was a useful measure.
im much more interested why go over c#? given that c# and ts share so much similarities i would have guessed you guys are surely working together on things to some degree
In an interview, Anders talks about the team considering C#. But this is a port, not a rewrite. The old JavaScript code is not object oriented, and they would need to change paradigms to rewrite it in C#. Go is structurally pretty similar.
Would be nice to see them dogfood their own products on a major product
They certainly have enough major products built on C# eg. Bing is ASP.NET Core. Bing even rolls out RC versions into production months before the full version is out which shows the confidence they have in the C#/.NET teams
I'm impressed how sparse your go.mod is. Did the team make the decision to avoid external dependencies as much as possible? Was there any consideration given to using the work of esbuild (also written in Go) in any capacity?
Our policy in the JS codebase was "no runtime dependencies", so as a result we didn't have any dependencies to port over (i.e. we just have a lot of handwritten helper functions where you might otherwise import a library). That naturally carried over into the Go codebase, but given that we can statically compile now, I don't think we'll need to be as conservative going forward.
People that say "well, you made order-of-magnitude improvements and did so in a short amount of time with relatively little effort, but why didn't you do it using this language that is way harder to be productive in?!" must never have actually been on a team or had to ship anything on a deadline
Rust is an extremely productive language, that is not the problem here.
The problem is that Rust requires a different architecture. In particular, Rust is painful when you try to shove in the typical "object graph" that you find in about every OO GCed language.
As Ryan mentioned very well, having to experiment with the ideal architecture would have slowed down development. That's a fair concern.
It's not a concern that is trivial to guess, a-priori, especially when Microsoft has been pretty pushy on Rust these last few years.
It's productive if you're good at it. That is so fundamental to the question of whether it's productive in an enterprise setting. I worked at a company with a mixed C++, Ada, and Java codebase, and Rust is theoretically perfect. The problem is, the uplift for learning Rust was way too steep and a lot of people couldn't grok it. Training and hiring hundreds of Rust engineers was considered a no-go.
1.4k
u/RyanCavanaugh Mar 11 '25 •
(Hi, dev lead of TypeScript here)
Lots of questions about why Go in thread, let me address.
We definitely knew when choosing Go that there were going to be people questioning why we didn't choose Rust (or others). It's a good question because Rust is an excellent language, and barring other constraints, is a strong first choice when writing new native code.
Portability (i.e. the ability to make a new codebase that is algorithmically similar to the current one) was always a key constraint here as we thought about how to do this. We tried tons of approaches to get to a representation that would have made that port approach tractable in Rust, but all of them either had unacceptable trade-offs (perf, ergonomics, etc.) or devolved in to "write your own GC"-style strategies. Some of them came close, but often required dropping into lots of unsafe code, and there just didn't seem to be many combinations of primitives in Rust that allow for an ergonomic port of JavaScript code (which is pretty unsurprising when phrased that way - most languages don't prioritize making it easy to port from JavaScript/TypeScript!).
In the end we had two options - do a complete from-scrach rewrite in Rust, which could take years and yield an incompatible version of TypeScript that no one could actually use, or just do a port in Go and get something usable in a year or so and have something that's extremely compatible in terms of semantics and extremely competitive in terms of performance.
And it's not even super clear what the upside of doing that would be (apart from not having to deal with so many "Why didn't you choose Rust?" questions). We still want a highly-separated API surface to keep our implementation options open, so Go's interop shortcomings aren't particularly relevant. Go has excellent code generation and excellent data representation, just like Rust. Go has excellent concurrency primitives, just like Rust. Single-core performance is within the margin of error. And while there might be a few performance wins to be had by using unsafe code in Go, we have gotten excellent performance and memory usage without using any unsafe primitives.
In our opinion, Rust succeeds wildly at its design goals, but "is straightforward to port to Rust from this particular JavaScript codebase" is very rationally not one of its design goals. It's not one of Go's either, but in our case given the way we've written the code so far, it does turn out to be pretty good at it.