30 Punkte von GN⁺ 2025-12-06 | 1 Kommentare | Auf WhatsApp teilen
  • Im Mittelpunkt steht der Unterschied in Philosophie und Wertesystem der drei Sprachen und ein Vergleich, welche Probleme jede von ihnen lösen will
  • Go wird als Sprache beschrieben, die Einfachheit und Stabilität in den Vordergrund stellt und durch minimale Features Zusammenarbeit und Wartbarkeit erleichtert
  • Rust strebt gleichzeitig Sicherheit und Performance an und gewährleistet mit einem komplexen Typsystem und einer Trait-Struktur Speichersicherheit
  • Zig wird als experimentelle Sprache dargestellt, die Entwicklern durch manuelle Speicherverwaltung und datenorientiertes Design die vollständige Kontrolle gibt
  • Die gegensätzlichen Ansätze der drei Sprachen zeigen das Wertesystem, das Programmiersprachen verkörpern, und machen deutlich, dass die Wahl davon abhängt, mit welcher Philosophie sich Entwickler identifizieren

Perspektive des Sprachvergleichs

  • Der Autor will nicht die Sprache untersuchen, die er bei der Arbeit verwendet, sondern durch Experimente mit neuen Sprachen die Wertesysteme der einzelnen Sprachen verstehen
  • Betont wird, dass nicht der bloße Vergleich von Feature-Listen entscheidend ist, sondern welche Trade-offs eine Sprache gewählt hat
  • Go, Rust und Zig überschneiden sich funktional in vielen Bereichen, unterscheiden sich aber in den Werten, die ihre Designer priorisiert haben
  • Wer die Philosophie jeder Sprache versteht, kann besser beurteilen, für welche Umgebungen und Zwecke sie geeignet ist

Go — eine Sprache der Einfachheit und Zusammenarbeit

  • Go zeichnet sich durch Minimalismus aus und hat die Eigenschaft, dass man „die gesamte Sprache im Kopf behalten kann“
    • Generics wurden erst nach 12 Jahren hinzugefügt, und Funktionen wie Tagged Unions oder Syntax Sugar für Fehlerbehandlung gibt es bis heute nicht
  • Neue Features werden nur mit großer Vorsicht hinzugefügt; dadurch gibt es viel Boilerplate-Code, aber auch hohe Sprachstabilität und Lesbarkeit
  • Go-Slices decken Funktionen ab, die in Rust Vec<T> oder in Zig ArrayList übernehmen, wobei die Laufzeitumgebung die Speicherorte automatisch verwaltet
  • Die Sprache entstand aus Unzufriedenheit mit der Komplexität von C++ und langsamen Kompilierzeiten und wurde mit dem Ziel einfacher und schneller Kompilierung entworfen
  • Im Fokus steht die Effizienz der Zusammenarbeit im Unternehmensumfeld; klare, konsistente Programme sind wichtiger als komplexe Features

Rust — komplex, aber mit starker Sicherheit und Performance

  • Rust wirbt mit „Zero-Cost Abstractions“ und ist eine maximalistische Sprache, in der viele Konzepte zusammenkommen
  • Dass Rust schwer zu lernen ist, liegt an der hohen Konzeptdichte sowie an seinem komplexen Typsystem und der Trait-Struktur
  • Das Kernziel von Rust ist die Verbindung von Performance und Speichersicherheit
    • Um UB (Undefined Behavior) zu verhindern, werden Prüfungen zur Compile-Zeit durchgeführt
    • Dadurch werden unvorhersehbare Laufzeitverhalten wie ungültige Pointer-Referenzen oder Double Free verhindert
  • Damit der Compiler das Laufzeitverhalten des Codes verstehen kann, müssen Entwickler Typen und Traits explizit definieren
  • Diese Struktur erhöht die Verlässlichkeit fremden Codes und trägt zu einem lebendigen Library-Ökosystem bei

Zig — vollständige Kontrolle und datenorientiertes Design

  • Zig ist unter den drei Sprachen die jüngste; sie befindet sich bei Version 0.14, und die Dokumentation der Standardbibliothek ist fast nicht vorhanden
  • Die Sprache setzt auf manuelle Speicherverwaltung, sodass Entwickler selbst alloc() aufrufen und den Allocator wählen müssen
  • Anders als Rust oder Go lassen sich globale Variablen einfach erzeugen; außerdem erkennt die Laufzeitumgebung „illegal behavior“ und beendet das Programm
    • Über vier wählbare Release-Modi beim Build lässt sich die Balance zwischen Performance und Stabilität anpassen
  • Auf objektorientierte Programmierung (OOP) wird bewusst verzichtet
    • Es gibt keine private Felder und kein dynamisches Dispatching, und selbst std.mem.Allocator ist nicht als Interface umgesetzt
    • Stattdessen verfolgt Zig datenorientiertes Design (data-oriented design)
  • Auch bei der Speicherverwaltung wird nicht eine feingranulare objektbezogene Verwaltung nach dem RAII-Prinzip bevorzugt, sondern eine Struktur empfohlen, in der große Speicherblöcke periodisch allokiert und freigegeben werden
  • Zig wird als freie, gegenkulturelle Sprache beschrieben, die OOP-Denkmuster entfernt und entwicklergetriebene Kontrolle maximiert
  • Das Team konzentriert sich derzeit auf die Neuschreibung aller Abhängigkeiten; ein stabiler Release 1.0 ist noch nicht terminiert

Fazit — Unterschiede in den Werten, die Sprachen sichtbar machen

  • Go stellt Zusammenarbeit und Einfachheit, Rust Sicherheit und Performance, Zig Freiheit und Kontrolle in den Mittelpunkt
  • Die Unterschiede zwischen den drei Sprachen sind nicht bloß ein Vergleich von Features, sondern spiegeln eine philosophische Entscheidung über Softwareentwicklung wider
  • Entwickler wählen eine Sprache letztlich danach, mit welchen Werten sie sich identifizieren

1 Kommentare

 
GN⁺ 2025-12-06
Hacker-News-Kommentare
  • In Rust ist es nicht schwer, eine mutable globale Variable zu erstellen
    Man muss nur unsafe oder Smart Pointer verwenden, die Synchronisierung bereitstellen.
    Rust ist standardmäßig re-entrant und garantiert Thread-Sicherheit zur Compile-Zeit.
    Wenn man sich nicht um statische Thread-Sicherheit kümmern will, kann man das genauso leicht machen wie in Zig oder C.
    Der Unterschied ist, dass Rust mehr Werkzeuge für Garantien über das Laufzeitverhalten des Codes bietet.

    • Aus der Perspektive von jemandem, der Rust seit vielen Jahren nutzt, sind mutable globale Variablen ein typisches Beispiel für „nur weil man es kann, sollte man es nicht tun“.
      Wenn ich in andere Sprachen zurückgehe und sehe, wie so etwas ganz selbstverständlich verwendet wird, fühlt sich das in Bezug auf Sicherheit wie Wahnsinn an.
    • Formulierungen wie „trivial, man braucht nur ~“ habe ich auch bei C++, Perl und Haskell gehört.
      Aber wenn sich solche „einfachen Dinge“ aufsummieren, sind sie am Ende überhaupt nicht einfach.
      Rust hat diese Schwelle bereits überschritten und ist jetzt keineswegs trivial.
    • Ich frage mich, ob der Rust-Compiler Race Conditions zwischen Threads zur Compile-Zeit erkennt.
      Wenn ja, wäre das attraktiver als C.
      Ich würde auch gern wissen, wie Fälle behandelt werden, in denen zwei Variablen immer zusammen gesperrt werden müssen.
    • Wenn ich eine Sprache entwerfen würde, würde ich mutable globale Variablen komplett verbieten.
      Beim Debuggen stellte sich am Ende immer heraus, dass dort die Ursache des Problems lag.
  • Zu dem Hinweis auf die konzeptionelle Dichte von Rust: Ich denke, dass man in der Praxis schon mit 5 % des Wissens produktiv sein kann.
    Ich benutze Rust seit über 12 Jahren, hatte aber nie Anlass, etwas wie #[fundamental] zu verwenden.
    Auch in Rust kann man Arena Allocation nutzen, und es gibt ein Allocator-Konzept.
    Es gibt nur einen Standard-Allocator, und typischerweise verwendet man explizite Heap-Allokationen wie Box::new.
    Ein mutable global lässt sich als static FOO: Mutex<T> = Mutex::new(...) anlegen, und für Speichersicherheit ist ein Mutex nötig.
    Rusts Typsystem ist nicht nur auf Speichersicherheit ausgelegt, sondern auch darauf, die semantische Sicherheit des Codes zu garantieren.

    • Da aber andere Entwickler andere 5–10 % der Konzepte verwenden können, muss man für Zusammenarbeit am Ende doch mehr Konzepte lernen.
      In C gibt es weniger solche Komplexität.
      Komplexität ist letztlich ein wichtiges Problem.
    • Die Aussage „Rust kann auch Arena Allocation“ stimmt, aber die meisten Rust-/Go-Programme haben im Standardpfad viele kleine Allokationen.
      Es geht nicht nur darum, ob etwas möglich ist, sondern um Unterschiede im grundlegenden Programmierstil.
    • Wenn der Allocator in Rust ein Typ ist, frage ich mich, ob man in einem m:n-Thread-Modell pro Request eine eigene Arena vergeben kann.
    • Es wird auch gefragt, ob Rusts Allocator global ist und ob alle Heap-Allokationen denselben Allocator verwenden.
    • Unter Verweis auf Casey Muratoris Video zu Batch Allocation wird angemerkt, dass manche Entwickler das missverstehen und damit Rusts RAII kritisieren.
      Es gab auch einen Fall, in dem die Zig Software Foundation Aussagen von Asahi Lina über Rust falsch zitierte.
      Diese Marketing-Haltung, andere Sprachen kleinzumachen, gefällt mir bei Zig nicht.
  • Ich mag Zig, weil es eine Sprache ist, die Speichererschöpfung elegant behandelt.
    Jede Allokation gilt als potenziell fehlschlagend und muss explizit behandelt werden.
    Auch Stack-Speicher wird nicht magisch behandelt; der Compiler analysiert den Aufrufgraphen und leitet die maximale Größe ab.
    In Embedded-Umgebungen ist ein solches ressourcenorientiertes Design unverzichtbar.

    • Auf Betriebssystemen wie Linux mit Overcommit treten echte Allokationsfehler allerdings nicht wirklich auf.
      Das lässt sich nicht auf Sprachebene lösen.
    • Auf die Frage, warum Zig existieren müsse, wenn es schon Rust gibt, würde ich eher fragen: „Warum Zig, wenn es schon C gibt?“
      Am Ende haben beide dasselbe Problem der manuellen Speicherverwaltung.
      Dann halte ich eine GC-Sprache für die bessere Wahl.
    • Ich frage mich, wie Zigs Stackgrößen-Inferenz bei Rekursion oder Aufrufen über Funktionszeiger funktioniert.
    • Zig ist nicht das erste Beispiel; man sollte sich die Geschichte der Systemsprachen seit JOVIAL von 1958 ansehen.
    • Auch in Rust kann man Pre-Allocation gut handhaben.
      Da die Rust-Standardbibliothek bei OOM jedoch panic verwendet, gibt es für Embedded-Entwicklung im no-std-Umfeld ein eigenes Ökosystem.
  • Gos Slices unterscheiden sich von Rusts Vec<T>.
    append() gibt ein neues Slice zurück, das den vorhandenen Speicher teilen kann oder auch nicht.
    Es gibt keine Möglichkeit, den Speicher zu verkleinern, und wenn man nur append(s, ...) schreibt, ignoriert man das neue Slice.
    Go hat die Haltung „mach einfach, was ich gesagt habe“, während Rust eher sagt: „prüfe, ob du getan hast, was ich gesagt habe“.
    Anders gesagt: Go erlaubt Fehler zugunsten von Einfachheit, Rust wählt den Weg, Fehler zu reduzieren, auch wenn es komplexer wird.

    • Tatsächlich kann man Speicher mit slices.Clip verkleinern.
      Außerdem führt schon append(s, ...) zu einem Compilerfehler, daher ist die ursprüngliche Aussage etwas ungenau.
      Go ist eine Sprache, die bei neuen Features den Anstieg an Komplexität vorsichtig abwägt.
    • Da append(s, …) nicht einmal kompiliert, denke ich nicht, dass Anfänger einen solchen Fehler machen können.
    • Es ist interessant, dass trotz Generics in Go Typen wie List[T] nicht weit verbreitet sind.
      Vermutlich deshalb, weil man eine vergrößerbare Liste nicht so oft direkt übergibt.
    • In Go sind die meisten Fallstricke (foot guns) in Spezifikation und Dokumentation klar beschrieben.
      Häufig lesen Leute die Dokumentation einfach nicht und sind dann überrascht.
  • Ich denke, UB (Undefined Behavior) in C/C++ per Laufzeitprüfung zu erfassen, ist praktisch schwer umsetzbar.
    Selbst Android hat Sanitizer auf alle Commits angewendet, aber erst nach dem Wechsel zu Rust gingen die Exploits zurück.

    • Es wird um eine Quelle für die Aussage zu den Android-Sanitizern gebeten.
  • Mir gefiel, dass der Vergleichsartikel die Stärken und Schwächen der einzelnen Sprachen ehrlich behandelt hat.
    Schade nur, dass Raku nicht erwähnt wurde.
    Meiner Ansicht nach bildet C–Zig–C++–Rust–Go ein Kontinuum der Low-Level-Sprachen, während auf der High-Level-Seite Julia–R–Python–Lua–JS–PHP–Raku–WL folgt.

    • Es wird gefragt, wofür WL steht.
    • Raku ist eine ausdrucksstarke Allzwecksprache mit eingebautem Multiple Dispatch, Roles, gradueller Typisierung, Lazy Evaluation und einem starken RegEx-System.
      Die Sprachdefinition wird auf Sprachebene unterstützt, was DSLs und Log-Parsing erleichtert.
      Da sie VM-basiert ist, ist die Performance geringer, aber sie eignet sich gut dafür, die Struktur eines Problems direkt auszudrücken.
      Als Nachfolger von Perl zielt sie auf eine flexible und konsistente Sprache.
  • Dass in Rust automatisch eine Heap-Allokation erfolgt, wenn eine Funktion einen Pointer zurückgibt, ist ein Missverständnis.
    Lokale Variablen liegen auf dem Stack und verschwinden beim Return, daher wird der Pointer ungültig.
    Rust erlaubt im sicheren Modus keine Pointer-Dereferenzierung, und im unsafe-Modus trägt der Entwickler die Verantwortung für die Gültigkeit.
    Vermutlich wurde Box::new mit einer „impliziten Allokation“ verwechselt.

    • Die Escape Analysis von Go mit Rusts expliziter Heap-Allokation zu verwechseln, ist schwer nachvollziehbar.
      Das wirkt entweder wie ein Missverständnis des Konzepts oder wie absichtliche Irreführung.
  • Gos größter Vorteil ist sein einfaches Nebenläufigkeitsmodell.
    Dank Goroutines kann man leicht parallelen Code schreiben.

    • Ein weiterer Vorteil von Go ist die Konsistenz des Codes, durch die sich große Codebasen leicht durchsuchen lassen.
      Implementierungen von Interfaces zu finden ist zwar schwierig, aber die Lesbarkeit ist hoch und damit für Teamarbeit vorteilhaft.
    • Rob Pikes Vortrag „Concurrency is not Parallelism“ erklärt Gos Nebenläufigkeitsphilosophie sehr gut.
      Es gibt keine colored functions, und kanalbasierte Kommunikation ist einfach, sodass man schnell korrekten nebenläufigen Code schreiben kann.
    • Ich halte Structured Concurrency allerdings für das einfachere Modell.
      Verwandter Artikel: Structured Concurrency or Go Statement Considered Harmful
    • Zigs neue std.Io-Schnittstelle ähnelt Gos Nebenläufigkeitsmodell.
      Das Schlüsselwort go entspricht std.Io.async, Channels entsprechen std.Io.Queue und select entspricht std.Io.select.
    • Erlang-Entwickler würden der Aussage wohl nicht zustimmen, dass Gos Nebenläufigkeitsmodell das einfachste sei.
  • Ich wünsche mir eine Sprache, die Gos Einfachheit mit Rusts Result-/Fehler-/Enum-Behandlung und besseren Generics verbindet.

    • Dem stimme ich zu. Es gibt eine große Marktnachfrage nach nativen Sprachen mit GC und zugleich stärkerem Typsystem.
      Ich habe mir OCaml, D, Swift, Nim, Crystal und andere angesehen, aber bisher hat keine Sprache den Markt erobert.
    • Ich habe gehört, dass modernes OCaml ein sehr starkes Nebenläufigkeitsmodell hat und leistungsmäßig mit Go konkurrieren kann.
    • Die Sprache Borgo war ein solcher Versuch, wurde aber eingestellt.
      Stattdessen könnte man sich Gleam ansehen.
    • Gos Error Proposal fand ich interessant.
      Ich hoffe, dass es Verbesserungen geben wird, die solche wiederkehrenden Probleme lösen.
      Generics bleiben wahrscheinlich weiterhin eine schwierige Aufgabe.
    • Die nächste Alternative ist wohl C#, aber es ist nach wie vor eine stark OOP-zentrierte Sprache.
  • Mir gefiel der allgemeine Ton des Textes, weil man die Leidenschaft und Neugier eines Nachwuchsentwicklers spürte.
    Dass Go lange keine Generics hatte, war nicht bloß minimalistischer Purismus, sondern das Ergebnis sorgfältig abgewogener Trade-offs.
    Rusts Lifetimes waren für viele Menschen die größte Hürde, und die Innovationskraft der Sprache liegt in der Kombination bestehender Konzepte.
    Zigs manuelle Speicherverwaltung basiert weniger auf dem Ausschluss von OOP als auf der Philosophie des Data-Oriented Design (DOD).
    Verwandter Vortrag: Andrews DOD-Präsentation

    • Wie Russ Cox 2009 in „The Generic Dilemma“ formulierte,
      war die Kernfrage: „Will man langsame Programmierer, langsame Compiler oder langsame Programme?“
      Das Go-Team scheint am Ende einen akzeptablen Kompromiss gefunden zu haben.