Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Why not AOT compiled C#, given the team's historical background?


There is an interview with Anders Hejlsberg here: https://www.youtube.com/watch?v=ZlGza4oIleY

The question comes up and he quickly glosses over it, but by the sound of it he isn't impressed with the performance or support of AOT compiled C# on all targeted platforms.


https://www.youtube.com/watch?v=10qowKUW82U

[19:14] why not C#?

Dimitri: Was C# considered?

Anders: It was, but I will say that I think Go definitely is -- it's, I'd say, the lowest-level language we can get to and still have automatic garbage collection. It's the most native-first language we can get to and still have automatic GC. In C#, it's sort of bytecode first, if you will; there is some ahead-of-time compilation available, but it's not on all platforms and it doesn't have a decade or more of hardening. It was not geared that way to begin with. Additionally, I think Go has a little more expressiveness when it comes to data structure layout, inline structs, and so forth. For us, one additional thing is that our JavaScript codebase is written in a highly functional style -- we use very few classes; in fact, the core compiler doesn't use classes at all -- and that is actually a characteristic of Go as well. Go is based on functions and data structures, whereas C# is heavily OOP-oriented, and we would have had to switch to an OOP paradigm to move to C#. That transition would have involved more friction than switching to Go. Ultimately, that was the path of least resistance for us.

Dimitri: Great -- I mean, I have questions about that. I've struggled in the past a lot with Go in functional programming, but I'm glad to hear you say that those aren't struggles for you. That was one of my questions.

Anders: When I say functional programming here, I mean sort of functional in the plain sense that we're dealing with functions and data structures as opposed to objects. I'm not talking about pattern matching, higher-kinded types, and monads.

[12:34] why not Rust?

Anders: When you have a product that has been in use for more than a decade, with millions of programmers and, God knows how many millions of lines of code out there, you are going to be faced with the longest tail of incompatibilities you could imagine. So, from the get-go, we knew that the only way this was going to be meaningful was if we ported the existing code base. The existing code base makes certain assumptions -- specifically, it assumes that there is automatic garbage collection -- and that pretty much limited our choices. That heavily ruled out Rust. I mean, in Rust you have memory management, but it's not automatic; you can get reference counting or whatever you could, but then, in addition to that, there's the borrow checker and the rather stringent constraints it puts on you around ownership of data structures. In particular, it effectively outlaws cyclic data structures, and all of our data structures are heavily cyclic.

(https://www.reddit.com/r/golang/comments/1j8shzb/microsoft_r...)


I wonder if they explored using a Gc like https://github.com/Manishearth/rust-gc with Rust. I think that probably removes all the borrow checker / cycle impedance mismatch while providing a path to remove Gc from the critical path altogether. Of course the Rust Gc crates are probably more immature, maybe slower, than Go’s so if there’s no path to getting rid of cycles as part of down-the-road perf optimization, then Go makes more sense.


>C# is heavily OOP-oriented, and we would have had to switch to an OOP paradigm to move to C#

They could have used static classes in C#.


he went into more detail about C# in this one: https://youtu.be/10qowKUW82U?t=1154s


He says:

- C# Ahead of Time compiler doesn't target all the platforms they want.

- C# Ahead of Time compiler hasn't been stressed in production as many years as Go.

- The core TypeScript compiler doesn't use any classes; Go is functions and datastructures whereas C# is heavily OOP, so they would have to switch paradigms to use C#.

- Go has better control of low level memory layouts.

- Go was ultimately the path of least resistance.



Anders explained his reasoning in this interview (transcript):

https://github.com/microsoft/typescript-go/discussions/411#d...


this is the "official" response at this point, since it is in the FAQ linked in the OP


I'm not involved in the decisions, but don't C# applications have a higher startup time and memory usage? These are important considerations for a compiler like this that needs to start up and run fast in e.g. new CI/CD boxes.

For a daemon like an LSP I reckon C# would've worked.


Yes, in fact that's one of the main reasons given in the two linked interviews: Go can generate "real" native executables for all the platforms they want to support. One of the other reasons is (paraphrasing) that it's easier to port the existing mostly functional JS code to Go than to C#, which has a much more OOP style.


The C# compiler is written in C# and distributed to multiple platforms. Along with the JIT that runs on all kinds of devices.

Graph for the differences in Runtime, Runtime Trimmed, and AOT .NET.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/n...


Not when compiled by NativeAOT. It also produces smaller binaries than Go and has better per-dependency scalability (due to metadata compression, pointer-rich section dehydration and stronger reachability analysis). This also means you can use F# too for this instead, which is excellent for langdev (provided you don't use printf "%A" which is incompatible which is a small sacrifice).


What is the cross compilation support for NativeAOT though? This is one of the things that Go shines (as long as you don't use CGO, that seems perfectly plausible in this project), and while I don't think it would be a deal breaker it probably makes things a lot easier.


What is the state of WASM support in Go though? :)

I doubt the ability to cross-compile TSC would have been a major factor. These artifacts are always produced on dedicated platforms via separate build stages before publishing and sign-off. Indeed, Go is better at native cross-compilation where-as .NET NativeAOT can do only do cross-arch and limited cross-OS by tapping into Zig toolchain.


> What is the state of WASM support in Go though? :)

The gc compiler considers it a first-class build target.

Like C#, the binary tends to be on the larger side[1], which makes it less than ideal for driving a marketing website, but that's not really a problem here. Installation of tools before use is already the norm.

[1] The tinygo compiler can produce very small WASM binaries, comparable to C, albeit with some caveats (surprisingly, the extra data gc produces isn't just noise).


> What is the state of WASM support in Go though? :)

I am sure it is good enough that the team decided to choose Go either way OR it is not important for this project.

> I doubt the ability to cross-compile TSC would have been a major factor.

I never said it was a major factor (I even said "I don't think it would be a deal breaker"), but it is a factor nonetheless. It definitely helps a lot during cross-platform debugging since you don't need to setup a whole toolchain just to test a bug in another platform, instead you can simple build a binary on your development machine and send it to the other machine.

But the only reason I asked this is because I was curious really, no need to be so defensive.


Native AOT exists, and C# has many C++ like capabilities, so not at all.


It exists but isn’t the same as a natively compiled binary. A lot gets packed into an AOT binary for it to work. Longer startup times, more memory, etc.


Just like Go, there is no magic here.

Where do you think Go gets those chubby static linked executables from?

That people have to apply UPX on top.


Go’s static binaries are orders of magnitude smaller than .Net’s static binaries. However, you are right, all binaries have some bloat in order to make them executable.


This is flat out incorrect if you are doing AOT in C#


How? What I said was fact. All executables have some bloat that makes them executables. Golang’s being the smaller. Whether it’s shared lib pointer or static…


Seeing that Hejlsberg started out with Turbo Pascal and Delphi, and that Go also has a lot of Pascal-family heritage, he might hold some sympathy for Go as well...


Yes there is that irony, however when these kind of decisions are made, by folks with historical roots on how .NET and C# came to be, then .NET team cannot wonder why .NET keeps lagging adoption versus other ecosystems, on companies that aren't traditional Microsoft shops.


Not involved, but there's a faq in their repo, and this answers your question, perhaps, a bit: https://github.com/microsoft/typescript-go/discussions/411


Thanks, but it really doesn't clarify why a team with roots on the .NET ecosystem decided C#/Native AOT isn't fit for purpose.


I don't understand what Anders' past involvement with C# has to do with this. Would the technical evaluation be different if done by Anders vs someone else?


C# and Go are direct competitors and the advantages of Go that were cited are all features of C# as well, except the lack of top level functions. That's clearly not an actual problem: you can just define a class per file and make every method static, if that's how you like to code. It doesn't require any restructuring of your codebase. There's also no meaningful difference in platform support, .NET AOT supports Win/Mac/Linux on AMD64/ARM i.e. every platform a developer might use.

He clearly knows all this so the obvious inference is that the decision isn't really about features. The most likely problem is a lack of confidence in the .NET team, or some political problems/bad blood inside Microsoft. Perhaps he's tried to use it and been frustrated by bugs; the comment about "battle hardened" feel like where the actual rationale is hiding. We're not getting the full story here, that's clear enough.

I'm honestly surprised Microsoft's policies allowed this. Normally companies have rules that require dogfooding for exactly this reason. Such a project is not terribly urgent, it has political heft within Microsoft. They could presumably have got the .NET team to fix bugs or make optimizations they need, at least a lot easier than getting the Go team to do it. Yet they chose not to. Who would have any confidence in adoption of .NET for performance sensitive programs now? Even the father of .NET doesn't want to use it. Anyone who wants to challenge a decision to adopt it can just point at Microsoft's own actions as evidence.


Thanks, this is a good way to frame it, someone else also phrased similar sentiment which I'm in total agreement with: https://x.com/Lon/status/1899527659308429333

It is especially jarring given that they are a first-party customer who would have no trouble in getting necessary platforms supported or projects expedited (like NativeAOT-LLVM-WASM) in .NET. And the statements of Anders Hejlsberg himself which contradict the facts about .NET as a platform make this even more unfortunate.


I wonder if there's just some cultural / generational stuff happening there too. The fact that the TS compiler is all about compiling a highly complex OOP/functional hybrid language yet is said to use neither objects nor FP seems rather telling. Hejlsberg is famous for designing object oriented languages (Delphi, C#) but the Delphi compiler itself was written largely in assembly, and the C# compiler was for a very long time written in C++ iirc. It's possible that he just doesn't personally like working in the sort of languages he gets paid to design.

There's an interesting contrast here with Java, where javac was ported to Java from C++ very early on in its lifecycle. And the Java AOT compiler (native image) is not only fully written in Java itself, everything from optimizations to code generation, but even the embedded runtime is written in Java too. Whereas in the .NET world Roslyn took quite a long time to come along, it wasn't until .NET 6, and of course MS rejected it from Windows more or less entirely for the same sorts of rationales as what Anders provides here.


> Roslyn

It was introduced back then with .NET Framework 4.6 (C# 6) - a loong time ago (July 2015). The OSS .NET has started with Roslyn from the very beginning.

> And the Java AOT compiler (native image) is not only fully written in Java itself, everything from optimizations to code generation, but even the embedded runtime is written in Java too.

NativeAOT uses the same architecture. There is no C++ besides GC and pre-existing compiler back-end (both ILC and RyuJIT drive it during compilation process). Much like GraalVM's Native Image, the VM/host, type system facilities, virtual/interface dispatch and everything else it could possibly need is implemented in C# including the linker (reachability analysis/trimming, kind of like jlink) and optimizations (exact devirtualization, cctor interpreter, etc.).

In the end, it is the TypeScript team members who worked on this port, not Anders Hejlsberg himself, which is my understanding. So we need to take this into account when judging what is being communicated.


> In the end, it is the TypeScript team members who worked on this port, not Anders Hejlsberg himself, which is my understanding

no? https://github.com/microsoft/typescript-go/graphs/contributo...


Ah, I see. Thanks for the clarification. Well, doubly unfortunate then. I wonder if we'll ever know what happened behind the scenes.


Ah C# 6 not .NET 6, thanks for the correction. Cool to hear that the NativeAOT stuff follows the same path.


Yea, I came here to say the same thing. Anders' reasons for not going with C# all seem either dubious or superficial and easily worked around.

First he mentions the no classes thing. It is hard to see how that would matter even for automated porting, because like you said, he could just use static classes, and even do a static using statement on the calling side.

Another one of his reasons was that Go was good at processing complex graphs, but it is hard to imagine how Go would be better at that than C#. What language feature that Go has, but C# does not supports that? I don't think anyone will be able to demonstrate one. This distinction makes sense for Go vs Rust, but not for Go vs C#.

As for the platform / AOT argument, I don't know as much about that, but I thought it was supposed to be possible now. If it isn't, it seems like it would be better for Microsoft to beef that up than to allow a vote of no confidence to be cast like this.


Yes, when the author of the language feels it is unfit for purpose, it is a different marketing message than a random dude on the Internet on his new startup project.


Pure speculation, but C# is not nearly the first class citizen that go binaries are when you look at all possible deployment targets. The “new” Microsoft likely has some built-in bias against “embrace and extend” architectural and business decisions for developers. Overall this doesn’t seem like a hard choice to me.

Cue rust devotees in 3, 2, ..


> Cue rust devotees in 3, 2, ..

If you are a rust devotee, you can use https://github.com/FractalFir/rustc_codegen_clr to compile your rust code to the same .NET runtime as C#. The project is still in the works but support is said to be about 95% complete.


Link to interview with Anders. (linked from the thread as well) https://www.youtube.com/watch?v=10qowKUW82U&t=1154s




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: