Gedanken zu Go vs. Rust vs. Zig
(sinclairtarget.com)- 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 ZigArrayListü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.Allocatorist nicht als Interface umgesetzt - Stattdessen verfolgt Zig datenorientiertes Design (data-oriented design)
- Es gibt keine private Felder und kein dynamisches Dispatching, und selbst
- 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
Hacker-News-Kommentare
In Rust ist es nicht schwer, eine mutable globale Variable zu erstellen
Man muss nur
unsafeoder 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.
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.
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.
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.
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.
In C gibt es weniger solche Komplexität.
Komplexität ist letztlich ein wichtiges Problem.
Es geht nicht nur darum, ob etwas möglich ist, sondern um Unterschiede im grundlegenden Programmierstil.
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.
Das lässt sich nicht auf Sprachebene lösen.
Am Ende haben beide dasselbe Problem der manuellen Speicherverwaltung.
Dann halte ich eine GC-Sprache für die bessere Wahl.
Da die Rust-Standardbibliothek bei OOM jedoch
panicverwendet, 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.
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.
append(s, …)nicht einmal kompiliert, denke ich nicht, dass Anfänger einen solchen Fehler machen können.Vermutlich deshalb, weil man eine vergrößerbare Liste nicht so oft direkt übergibt.
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.
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.
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::newmit einer „impliziten Allokation“ verwechselt.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.
Implementierungen von Interfaces zu finden ist zwar schwierig, aber die Lesbarkeit ist hoch und damit für Teamarbeit vorteilhaft.
Es gibt keine colored functions, und kanalbasierte Kommunikation ist einfach, sodass man schnell korrekten nebenläufigen Code schreiben kann.
Verwandter Artikel: Structured Concurrency or Go Statement Considered Harmful
std.Io-Schnittstelle ähnelt Gos Nebenläufigkeitsmodell.Das Schlüsselwort
goentsprichtstd.Io.async, Channels entsprechenstd.Io.Queueundselectentsprichtstd.Io.select.Ich wünsche mir eine Sprache, die Gos Einfachheit mit Rusts Result-/Fehler-/Enum-Behandlung und besseren Generics verbindet.
Ich habe mir OCaml, D, Swift, Nim, Crystal und andere angesehen, aber bisher hat keine Sprache den Markt erobert.
Stattdessen könnte man sich Gleam ansehen.
Ich hoffe, dass es Verbesserungen geben wird, die solche wiederkehrenden Probleme lösen.
Generics bleiben wahrscheinlich weiterhin eine schwierige Aufgabe.
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
war die Kernfrage: „Will man langsame Programmierer, langsame Compiler oder langsame Programme?“
Das Go-Team scheint am Ende einen akzeptablen Kompromiss gefunden zu haben.