- Das TypeScript-Team entschied sich für eine Portierung auf Go anstelle einer kompletten Neuentwicklung, um Fehlermeldungen und Semantik beizubehalten.
- Die automatische Speicherbereinigung und die First-Class Closures von Go waren für die Handhabung der komplexen Datenstrukturen des Compilers unerlässlich.
- Rusts Borrow-Checker hätte manuelle Workarounds für Zirkelbezüge erzwungen und dadurch unnötige Komplexität hinzugefügt.
- Go bot ausgereifte native Codegenerierung und Shared-Memory-Parallelität ohne zusätzlichen Aufwand.

Als das TypeScript-Team beschloss, seinen Compiler auf eine neue Sprache zu portieren, verfolgte es ein klares Ziel: Alles sollte exakt so funktionieren wie bisher. Das bedeutete, die Fehlermeldungen und die Semantik beizubehalten, auf die sich Entwickler seit Jahren verlassen hatten. Laut Anders Hejlsberg, dem leitenden Architekten, kam eine vollständige Neuentwicklung nicht in Frage, da sie die Abwärtskompatibilität gefährdet hätte. Stattdessen entschied man sich für eine Portierung – und diese Entscheidung ebnete den Weg für eine überraschende Wahl: Rust.
Für die Portierung wurde eine Sprache benötigt, die die komplexen internen Strukturen des Compilers ohne größere Änderungen verarbeiten konnte. Dem Team wurde schnell klar, dass automatische Speicherbereinigung und erstklassige Closures unerlässlich waren. Go bietet beides standardmäßig, zusammen mit ausgereifter nativer Codegenerierung und Shared-Memory-Concurrency auf allen gängigen Plattformen. Rust hingegen hätte erhebliche manuelle Anpassungen erfordert, insbesondere für die zirkulären Datenstrukturen des Compilers.
Warum sollte man Rust für den Hafen in Kauf nehmen?
Hejlsberg erklärte, der Compiler sei voll von Elternzeigern, rekursiven Typen und Symbolen, die aufeinander verweisen. Dadurch entstehen zirkuläre Referenzen, die in einer Sprache mit automatischer Speicherbereinigung (Garbage Collection) natürlich vorkommen. Die Go-Laufzeitumgebung bewältigt dies nahtlos, sodass sich das Team auf die Portierung konzentrieren kann, anstatt gegen die Sprache anzukämpfen. Rusts Borrow-Checker ist zwar leistungsstark für die Speichersicherheit, erlaubt diese Struktur aber nicht ohne unsicheren Code oder Tricks mit Referenzzählung. Das würde die Komplexität und das Risiko erhöhen, ohne einen klaren Nutzen zu bringen.
Beim Vergleich der beiden Sprachen fand das Team für Rust keine signifikanten Vorteile hinsichtlich Codegenerierung oder Parallelverarbeitung. Die native Codegenerierung von Go ist bereits ausgereift, und die Goroutinen bieten ein einfaches und effektives Modell für die parallele Ausführung. Rust mag in einigen Randfällen etwas besser abschneiden, doch der zusätzliche Aufwand, den Compiler mit den Besitzregeln von Go kompatibel zu machen, war nicht gerechtfertigt. Die Portierung sollte pragmatisch sein und keine Vorführung von Sprachmerkmalen darstellen.
Kompatibilität und Semantik: Die oberste Priorität
Der Hauptgrund für die Portierung war die Beibehaltung des identischen Verhaltens. Entwickler sind auf die Fehlermeldungen von TypeScript angewiesen , um ihren Code zu debuggen, und jede Änderung könnte ihre Arbeitsabläufe beeinträchtigen. Durch die Portierung nach Go konnte das Team die bestehende Logik und die Datenstrukturen wiederverwenden und so die Byte-für-Byte-Kompatibilität der Ausgabe sicherstellen. Dieser Ansatz reduziert zudem das Risiko, subtile Fehler einzuführen, die eine komplette Neuentwicklung mit sich bringen könnte.
Die automatische Speicherbereinigung von Go war ein entscheidender Faktor. Der interne Knoten- und Referenzgraph des Compilers ist stark vernetzt, und die manuelle Speicherverwaltung wäre extrem aufwendig. Mit Go kann das Team Speicher automatisch allozieren und freigeben und sich so auf die Compilerlogik konzentrieren. Erstklassige Closures erleichterten zudem die Implementierung der verschiedenen Durchläufe und Transformationen des Compilers, da sie den Kontext auf natürliche Weise erfassen können.
Rusts Ausleihprüfung: Ein K.O.-Kriterium
Rusts Borrow-Checker soll Datenkonflikte und Speicherfehler zur Kompilierzeit verhindern, unterliegt aber strengen Regeln. Die Datenstrukturen des TypeScript-Compilers sind voller Zyklen und gemeinsam genutzter Referenzen, die der Borrow-Checker ablehnt, sofern man nicht unsichere Blöcke oder `Rc`/`RefCell` verwendet. Hejlsberg merkte an, dass dies manuelle Workarounds für jede zirkuläre Datenstruktur erzwingen und dadurch zusätzlichen Code erzeugen und die Wartbarkeit des Codes erschweren würde. Es gab keinen Vorteil hinsichtlich Codegenerierung oder Parallelverarbeitung, der diesen Mehraufwand rechtfertigen würde.
Letztendlich war die Entscheidung eindeutig. Go bot die optimale Balance aus Einfachheit, Leistung und Kompatibilität. Die Portierung ist bereits im Gange, und das Team ist zuversichtlich, dass sie das gewohnte TypeScript-Erlebnis mit einem schnelleren und effizienteren Compiler bieten wird. Für Entwickler bedeutet das: keine Überraschungen – einfach das gleiche zuverlässige Werkzeug, das sie schon immer verwendet haben, jetzt auf einer moderneren Basis.
Alles in allem fiel die Entscheidung für Go anstelle von Rust bei der TypeScript-Portierung aus praktischen Gründen. Der Bedarf an automatischer Speicherbereinigung, erstklassigen Closures und der nahtlosen Behandlung zirkulärer Referenzen machte Go zur idealen Wahl. Rusts Sicherheitsgarantien sind zwar beeindruckend, aber sie haben einen Preis, den das TypeScript-Team nicht zahlen wollte. Das Ergebnis ist eine Portierung, die all das bewahrt, was Entwickler an TypeScript schätzen, und gleichzeitig die Grundlage für zukünftige Verbesserungen schafft.