- Bei der Speicherverwaltung bietet Zig einen einfacheren und intuitiveren Ansatz als Rust
- Der Borrow Checker von Rust ist leistungsfähig, verursacht bei der Entwicklung kleiner CLI-Tools aber übermäßige Komplexität und Belastung für Entwickler
- Mit der manuellen Speicherverwaltung von Zig lässt sich mit den richtigen Werkzeugen und etwas Entwicklerdisziplin dennoch effizient Speichersicherheit erreichen
- Für die Sicherheit eines Programms sind neben der Speichersicherheit auch vorhersehbares Verhalten, beherrschbare Performance und Datenschutz wichtig
- Rust eignet sich für große Systeme, aber für kleine praktische CLI-Tools ist Zig hinsichtlich Entwicklungsproduktivität und Wartbarkeit oft vorteilhafter
Überblick
In letzter Zeit wähle ich beim Bau von CLI-Tools bevorzugt Zig statt Rust.
Grundlagen der Speicherverwaltung: Stack und Heap
- Der Stack ist ein schneller Speicherbereich mit fester Größe, in dem sehr kurzlebige Daten wie Funktionsparameter, lokale Variablen und Rücksprungadressen gespeichert werden
- Der Heap ist ein Bereich für dynamische Speicherallokation und wird verwendet, wenn Daten länger leben oder ihre Größe erst zur Laufzeit feststeht
- Der Stack ist strukturell einfach, aber im Platz begrenzt, während man beim Heap Geschwindigkeit und Fragmentierung im Blick behalten muss
Rusts Borrow Checker
- Der Borrow Checker von Rust garantiert Speichersicherheit zur Compile-Zeit
- Er erzwingt Regeln für Referenzen, Ownership und Lifetimes und verhindert dadurch Fehler wie Nullpointer-Dereferenzierung oder Dangling Pointer im Vorfeld
- Allerdings wird Speichersicherheit nur zur Compile-Zeit geprüft; Benutzerfehler oder komplexe Probleme im Ownership-Design selbst verschwinden dadurch nicht
Fallbeispiel: mein eigenes Notes-CLI
- Beim Versuch, ein CLI zur Verwaltung persönlicher Notizen in Rust zu schreiben, musste ich wegen des Borrow Checkers die Struktur mühsam neu entwerfen
- In Zig hingegen war es mit einem Allocator viel einfacher, pointerbasierte Indizes zu erzeugen und frei zu ändern oder zu löschen
- Der Borrow Checker von Rust hat einen klaren Zweck, aber Zig ermöglicht schon mit grundlegendem Wissen über Speicherverwaltung und etwas Disziplin ein hohes Maß an Effizienz und Sicherheit
Sicherheit von CLI-Tools: mehr als nur Speichersicherheit
- Zur echten Sicherheit eines Produkts gehören viele Faktoren wie vorhersehbares Verhalten, aussagekräftiges Feedback bei Fehlern, Schutz sensibler Daten und Widerstandsfähigkeit gegen Angriffe
- Weder Rust noch Zig kann als „sicher“ gelten, wenn neben der Speichersicherheit andere Bedingungen nicht erfüllt sind
- Wenn ein CLI etwa in Fehlersituationen stillschweigend Daten überschreibt oder Dateiberechtigungen falsch setzt, kann das für Nutzer schwerwiegende Probleme verursachen
-
Sicherheit von CLI-Tools
- Vorhersehbares Verhalten: Auch bei fehlerhaften Eingaben oder unerwarteten Situationen muss konsistentes und klares Verhalten gewährleistet sein
- Vermeidung von Abstürzen und Datenbeschädigung: Fehler sollten elegant behandelt werden, ohne Daten zu beschädigen oder unbekannte Abstürze auszulösen
- Performance-Kontrolle: Auch bei großen Datenmengen dürfen Ressourcenverbrauch und Reaktionsfähigkeit nicht außer Kontrolle geraten
- Schutz sensibler Informationen: Temporäre Dateien und Berechtigungseinstellungen erfordern besondere Aufmerksamkeit
- Widerstandsfähigkeit gegen Angriffe: Robuste Eingabevalidierung sowie Schutz vor Speicherüberläufen und Injection-Angriffen sind nötig
Stärken und Grenzen von Rusts Borrow Checker
-
Stärken
- Verhinderung von Data Races und problematischen Mehrfachreferenzen: Der Compiler garantiert die Regeln für eine einzige mutable Referenz und mehrere immutable Referenzen
- Starke Garantien zur Compile-Zeit: Die meisten speicherbezogenen Bugs werden vor der Ausführung abgefangen
- Frühes Erkennen von Bugs: Besonders bei Produktivsystemen und nebenläufigen Systemen ein großer Vorteil
-
Grenzen und Unbequemlichkeiten
- Kognitiver Overhead: Selbst bei kleinen CLI-Aufgaben muss man sich zwangsläufig mit Ownership, Lifetimes und Referenzverwaltung beschäftigen
- Boilerplate / strukturelle Verzerrung: Wrapper wie
RcoderRefCell, exzessivescloneund Umstrukturierungen führen dazu, dass man statt auf „Problemlösung“ eher auf „den Compiler zufriedenstellen“ fokussiert ist - Schwach bei logischen und zustandsbezogenen Bugs: Nur Speicherregeln werden garantiert; Vorhersehbarkeit, Logikfehler und Datenintegrität dagegen nicht
- Komplexität in Edge Cases: Bei Caches, globalem Zustand oder mutablen Indizes treten Lifetime-Konflikte leicht auf
- Dadurch wird Rusts Borrow Checker in kleinen CLI-Projekten letztlich zu einer „mentalen Steuer“ für Entwickler und kann unnötig komplex werden
Zigs Ansatz zu Sicherheit und Einfachheit
- Zig basiert auf optionalen Sicherheitsprüfungen und manueller Speicherverwaltung
- Das Konzept des Allocators ist eingebaut, sodass sich strukturierte und vorhersehbare Speichernutzung umsetzen lässt
- Man kann auch eigene Allocators erstellen und die Speicherverwaltung an die Eigenschaften des jeweiligen Projekts anpassen
- Dank Zigs
defer-Syntax sind automatische Freigabe beim Verlassen eines Gültigkeitsbereichs und allgemeines Ressourcen-Cleanup deutlich intuitiver - Anders als bei Rust wird die Verantwortung des Entwicklers betont, was Disziplin erfordert; bei guter struktureller Gestaltung lässt sich Speichersicherheit jedoch leicht erreichen und bewahren
- In Zig ist der Code kompakt, und Änderungen an Strukturen wie Pointern, Listen und Indizes sind im Vergleich zu Rust deutlich einfacher
- Auch ohne die Einschränkungen wie in Rust lassen sich Code und Effizienz auf demselben Sicherheitsniveau umsetzen
- Zusätzlich hilft Zigs comptime-Funktion stark bei Codeausführung, Tests und Optimierungen zur Compile-Zeit
Bedeutung der Developer Experience
- Developer Experience (Ergonomics) umfasst Aspekte wie Sprachsyntax, Tooling, Dokumentation und Community
- Rust garantiert dank seiner sehr strengen Regeln letztlich Speichersicherheit, doch zu viele Regeln und zu viel Ceremony senken die Produktivität
- Zig betont ein entwicklergesteuertes Design, wodurch sich Code leichter und schneller schreiben, ändern und verstehen lässt
- Mit intuitivem Code, schnellen Iterationen und geringer mentaler Belastung hilft Zig Entwicklern, sich auf die Problemlösung statt auf den Kampf mit dem Werkzeug zu konzentrieren
- Zig vertraut dem Entwickler und gibt passende Werkzeuge und Wahlfreiheit, während Rust sich mitunter zu bevormundend und einschränkend anfühlen kann
- Eine entwicklerfreundliche Umgebung schützt nicht nur „vor Fehlern“, sondern ermöglicht auch, durch eigene Fehler zu lernen und sich weiterzuentwickeln
Fazit
- Für Bereiche, in denen Rust seine Stärken maximal ausspielt – etwa große, multithreaded, langlaufende Systeme – bleibt Rust weiterhin die beste Wahl
- Für kleine, praktische CLI-Tools sind Zigs Leichtgewichtigkeit, Einfachheit sowie schnelle Umsetzung und Wartung jedoch oft besser geeignet
- Speichersicherheit ist nur ein Teil des Sicherheits-Puzzles; vorhersehbares Verhalten, Wartbarkeit und Robustheit – alles essenzielle Eigenschaften von CLI-Tools – lassen sich in Zig oft leichter erreichen
- Letztlich geht es nicht um die „bessere Sprache“, sondern um die Wahl des Werkzeugs, das zum eigenen Workflow und zu den Anforderungen des Projekts passt
- Zig ist für die Entwicklung kleiner Tools eine Sprache, in der „Speichersicherheit + geringe mentale Kosten + Entwicklerfreundlichkeit/Produktivität“ hervorragend zusammenkommen
3 Kommentare
Ich habe den Eindruck, dass sich das Ökosystem noch nicht so stabilisiert hat wie bei Rust.
Da Zig in neuen Versionen ziemlich häufig Breaking Changes mit sich bringt, sollte man selbst bei kleinen Projekten möglichst CI einrichten und sie kontinuierlich pflegen.
Hacker-News-Kommentar
Der Vorteil von Zig ist, dass man weiterhin wie ein C-Entwickler denken kann, aber ich halte das bis zu einem gewissen Grad einfach für eine Frage der Gewöhnung
Entwickler, die mit Rust ausreichend vertraut sind, kämpfen nicht mehr mit dem Borrow Checker, sondern denken bereits in solchen Code-Strukturen
In Rust funktioniert ein Ansatz wie „Objektsuppe (object soup)“ nicht besonders gut, aber ich glaube nicht, dass das grundsätzlich der einfachere Weg ist; es fühlt sich nur leicht an, weil wir daran gewöhnt sind
Wenn man die Prämisse akzeptiert, dass sich Ergonomie (Nutzererfahrung) schwer messen oder quantifizieren lässt, bleiben solche Diskussionen zwangsläufig vage
Die Erzählung vom „Kampf mit dem Borrow Checker“ stammt aus einer Zeit, in der man in Rust nur lexikalische Lifetimes verstand
Meiner Erfahrung nach streuen erfahrene Rust-Entwickler überall
Arcein und benutzen es praktisch wie automatische Garbage CollectionIch habe viele Open-Source-Rust-Projekte gesehen, in denen auch erfahrene Rust-Entwickler überall
Arc,Clone,Copyusw. einsetzenDie Stärke von Zig ist, dass man wie in C vertraut entwickeln kann und zugleich Funktionen in Sprache und Tooling bekommt, die Sicherheit verbessern
Ich stimme dem Inhalt des Originals größtenteils nicht zu
Auch in Rust muss man wie in C oder Zig über Lifetimes, Ownership und Borrow Scopes nachdenken; der Unterschied ist nur, dass der Compiler hilft
Menschen machen Fehler, egal wie klug sie sind, besonders wenn sie müde oder abgelenkt sind; Fehler einzugestehen ist gerade ein Zeichen von Klugheit
Der Raum an Programmen, die der Rust-Compiler als sicher akzeptiert, ist nicht groß genug, daher weist er ziemlich oft völlig normale Programme zurück
Beispiel: Wenn in einer Struktur
Foobarundbazjeweils Strings sind, lässt sich nicht kompilieren, wenn manbarmutabel referenziert und anschließendbazunveränderlich referenzieren will; in solchen Fällen muss man die Code-Struktur künstlich umarbeitenDagegen lässt sich einwenden, dass schon die Notwendigkeit, den Code in eine zweit- oder drittbeste Architektur umzubauen, nur um „Fälle zu vermeiden, die in Wirklichkeit unproblematisch sind, aber vom Compiler abgelehnt werden“, eine große Last ist
Das obige Beispiel scheint mir ein wirklich hervorragender Fall zu sein; ich würde gern fragen, ob ich es in meinem Blog oder Artikel behandeln darf
Beim Ansehen des Codes bin ich mir meiner ursprünglichen Behauptung im Gegenteil noch unsicherer geworden
Wir sollten uns daran erinnern, dass nicht jedes Programm zwingend so „sicher“ sein muss
Viele von uns sind mit unsicherer Software aufgewachsen und hatten Spaß damit: Star Fox 64, MS Paint, FruityLoops usw.
Ich habe gelesen, dass Zig-Gründer Andrew Kelley Zig entwickelt hat, weil ihm eine Entwicklungsumgebung für Musikproduktionssoftware (DAW) fehlte; ich denke, Zig passt gut zu solcher kreativen Software
Wer empfindlich auf Speicherfehler reagiert, kann einfach Rust verwenden
Ich glaube sogar, dass Super Mario World wegen seiner Speicherfehler unterhaltsamer war
„Sicher“ ist letztlich eine Kurzform für „Mein Programm verhält sich wie beabsichtigt“
unsafeumsetzenIch bin etwas verwirrt und frage mich, ob du meine Aussage deshalb für eine schlechte Meinung hältst, weil sie so verstanden wird, als sei Memory Safety nicht wichtig
Schade fand ich, dass der Wert des Borrow Checkers unterschätzt wurde
Rusts Borrow Checker garantiert ungültige Speicherzugriffe bereits zur Compile-Zeit
Natürlich ist es unbequem, den Code so umzubauen, dass er den Compiler-Regeln entspricht
Wenn ich Rust für mich allein verwendet habe, hatte ich nie das Gefühl, dass Lifetime-Annotationen „falsch“ seien; sie waren eher lästige Routinearbeit, an die man sich aber schnell gewöhnt
Solange man kein
unsafebenutzt, können in Rust nicht zwei Threads gleichzeitig in denselben Speicher schreibenIch kann nicht nachvollziehen, „warum Zig sich bei CLI-Tools praktischer anfühlt“; Rust hat bei der Vermeidung von CVEs nach wie vor Vorteile
Tatsächlich erledige ich die meisten Aufgaben auch problemlos in GC-Sprachen, und wenn ich zu Projekten in anderen Sprachen beitrage, ist mir Rust, Zig oder C/C++ gleichermaßen recht
Gibt es an CLI-Tools irgendetwas Besonderes?
Dass ohne
unsafenicht zwei Threads gleichzeitig in denselben Speicher schreiben können, ist nicht ganz so eindeutigIch stimme zu, dass Backlinks in Rust viel zu kompliziert umzusetzen sind
Mit
Rc,Weak,RefCell,.borrow()usw. geht es, aber einfach ist es nichtFür kurzlebige Programme kann Arena-Allocation eine Lösung sein, und vermutlich ist genau das mit CLI-Tools gemeint
Rust spielt seine echte Stärke in riesigen, multithreaded Anwendungen aus, die lange laufen
Ich habe tatsächlich einen großen Metaverse-Client in Rust geschrieben, und selbst mit Dutzenden Threads, die 24 Stunden am Tag liefen, gab es weder Speicherlecks noch Abstürze
Dasselbe in C++ zu bauen würde ein QA-Team und Tools wie Valgrind praktisch voraussetzen, und Script-Sprachen wären dafür leistungsmäßig zu langsam
Ich habe mit Rust auch ein Physik-Simulationsflugzeug gebaut, das sogar Erdkrümmung und Gravitationsabweichungen berücksichtigt
Zig ist durchaus attraktiv, aber D existiert immer noch, und persönlich fühlt sich D für mich eher wie der C/C++-Ersatz an, den ich haben möchte
Die Syntax von Zig wirkt irgendwie ungewohnt, und Rust steht im Ökosystem bereits im Zentrum
Auch Go hat in vielen Sprach-Tools einen hohen Anteil und wird im KI-Bereich nach Python am häufigsten verwendet
Vor Rust gab es schon die Debatte Go vs. D, und ich hatte mir damals sogar ein D-Lehrbuch gekauft, bin dann aber doch zu Go gewechselt
int64waren intuitiverD ist gut, aber es fehlt die Killer-Anwendung, die es wirklich massentauglich macht
Ich verstehe nicht so recht, warum man für CLI-Tools unbedingt Rust oder Zig verwenden sollte
Der Flaschenhals ist I/O, nicht ein langsamer GC
GC-Fragen sind aus meiner Sicht kein Thema, solange man nicht in stark speicherintensiven Bereichen wie Spielen oder Datenbanken arbeitet
Ich möchte eher betonen, dass man mehr darüber nachdenken sollte, warum man überhaupt eine Sprache ohne GC wählen will, statt nur über Memory Safety zu streiten
Wenn man sie einfach benutzt, weil „No-GC Spaß macht“, dann ist das für sich genommen schon völlig ausreichend; dafür braucht es keine Debatte
Sofortige Startzeit ohne Anlaufverzögerung ist sehr nützlich
CLI-Tools in Go zu bauen war sehr angenehm, auch wenn ich die Sprache Go selbst nicht besonders mag
Ich bevorzuge zuerst Sprachen mit Sum Types, Pattern Matching und Async-Support
Zur Aussage, Entwicklung ohne GC betreffe nur den Spielebereich
Die GC-Debatte wirkt auf mich etwas wie ein Bandwagon
Ich habe mit Rusts eingebautem Borrowing/Referencing ein einfaches Notiz-Tool gebaut, und es war nicht so kompliziert wie erwartet
Wenn man sich eine Struktur vorstellt, in der Indizes einer
notes-Liste gespeichert und über eine Map verbunden werden, gibt es kaum Geschwindigkeitsunterschiede und keine SicherheitsnachteileSelbst wenn man sich bei einem Index irrt, wird das als Out-of-Bounds-Fehler erkannt, was immer noch viel besser ist, als Kernel-Speicher zu überschreiben
Auch
printf-Debugging ist so viel einfacher und intuitiverRaw Pointer oder References braucht man normalerweise nur dort, wo es wirklich nötig ist, etwa bei Allocators oder Async-Runtimes; für allgemeine Logik ist ein indexbasierter Ansatz passender
Bekanntlich kann Rust Async keine self-referential structs verwenden, weshalb dort auch die Probleme rund um
PinentstehenPointer auf Werte, die in
vecgespeichert sind, werden bei Reallocations usw. ungültig; in solchen Fällen meldetMirisofort einen FehlerWenn ich als C++-Entwickler nach einer sicheren Sprache suchen würde, erschiene mir Swift am geeignetsten
An vertraute oder ähnliche Sprachen gewöhnt man sich meist schneller
Swift hat seine Cross-Platform-Unterstützung in letzter Zeit ausgebaut, und mehrere aktive Mitglieder des C++-Standardisierungsgremiums sind daran beteiligt
Wegen der Apple-Nähe und dem Fehlen nativer UI-Frameworks ist die Ausbreitung außerhalb der Apple-Welt aber vergleichsweise begrenzt
Ich hoffe, dass Swift noch deutlich populärer wird
Falls jemand gute Ressourcen zum Vergleich von Swift und Zig/C kennt, würde ich Empfehlungen begrüßen
Es heißt, man könne mit Zig mit etwas Vorsicht speichersichere Software schreiben, aber im Grunde gilt das auf einem gewissen Niveau auch für C, wenn man diszipliniert genug arbeitet
Am Ende fehlt es in der Realität oft genau an dieser „kleinen Portion Disziplin (Training)“, und daraus entstehen dann die Probleme
Zig löst darüber hinaus zusätzlich die folgenden Probleme
comptime-Optimierungen und Build-Zeiten, die um ein Vielfaches schneller als bei C++/Rust sind, sind große StärkenWenn C nach mehr als 50 Jahren an genau diesem Disziplinproblem gescheitert ist, dann ist das schwieriger als jeder Weg des Shaolin-Mönchs