2 Punkte von GN⁺ 2025-07-14 | 1 Kommentare | Auf WhatsApp teilen
  • Mit der Einführung der neuen asynchronen I/O-Schnittstelle von Zig können Aufrufer die Implementierungsweise von I/O nun selbst auswählen und injizieren
  • Die neu entworfene Io-Schnittstelle unterstützt gleichzeitig Asynchronität und Parallelität und legt den Fokus auf Code-Wiederverwendung und Optimierung
  • Es sollen verschiedene Implementierungen in der Standardbibliothek bereitgestellt werden, darunter Blocking I/O, Event Loop, Thread-Pool, Green Threads und stacklose Coroutines
  • Über die neue API werden Future-Canceling und Ressourcenverwaltung sowie Buffering und granulareres Ein-/Ausgabeverhalten ermöglicht
  • Das bisherige Problem des Function Coloring wird gelöst, sodass sich mit einer einzigen Bibliothek sowohl synchroner als auch asynchroner Betrieb optimieren lässt

Überblick

Zig entwickelt sich derzeit in eine Richtung weiter, in der mit dem Entwurf einer neuen asynchronen I/O-Schnittstelle vor allem Flexibilität bei I/O-Operationen und Unterstützung für Parallelität im Mittelpunkt stehen. Diese Änderung trennt sich vom bisherigen async/await-Paradigma, sodass Programmierer in der Praxis deutlich vielfältigere I/O-Strategien einsetzen können.

Die neue I/O-Schnittstelle

Bisher wurden I/O-bezogene Objekte direkt im Code erzeugt und verwendet, nun wird die Io-Schnittstelle vom Aufrufer injiziert.

  • Dieser Ansatz ähnelt dem Allocator-Muster: Die aufrufende Seite wählt eine konkrete I/O-Implementierung aus und injiziert sie
  • So lässt sich auch in Code externer Pakete eine I/O-Strategie konsistent anwenden

Wesentliche Änderungen

  • Die Io-Schnittstelle übernimmt jetzt auch Konkurrenzoperationen (concurrency)
  • Wenn Code Konkurrenz korrekt ausdrückt, kann je nach Implementierung von Io auch Parallelität (parallelism) bereitgestellt werden

Codebeispiele

  • Es werden zwei Varianten verglichen: Code ohne Konkurrenz (seriell) und Code, in dem mit io.async und await die Möglichkeit zur Parallelisierung ausgedrückt wird
    • Serieller Code: Speichert nacheinander in zwei Dateien, ohne Chancen auf Parallelität zu nutzen
    • Paralleler Code: Nutzt Futures zum Speichern von Dateien und arbeitet in einer asynchronen Event Loop effizienter

Kombination von await und try

  • Bei gemeinsamer Verwendung von await und try besteht das Problem, dass bei einem Fehler in einem Future die Ressourcen eines anderen Future nicht freigegeben werden
  • Mit defer und future.cancel lässt sich korrektes Canceling und Aufräumen explizit ausdrücken

Future.cancel API

  • Future.cancel() und Future.await() sind idempotent (mehrfache Aufrufe haben keine Nebenwirkungen)
  • Wird cancel bei einem bereits abgeschlossenen Future aufgerufen, werden nur Ressourcen freigegeben; noch nicht abgeschlossene Aufgaben geben error.Canceled zurück

I/O-Implementierungen der Standardbibliothek

Die Io-Schnittstelle ist eine auf Runtime-Polymorphie basierende Schnittstelle, die direkt implementiert oder über Implementierungen aus Drittanbieterpaketen genutzt werden kann. Zigs Standardbibliothek soll verschiedene Typen von I/O-Implementierungen bereitstellen.

  • Blocking I/O: Verwendet schlicht das bestehende Blocking-I/O im C-Stil, ohne zusätzlichen Overhead
  • Thread-Pool: Verteilt Blocking-I/O auf einen OS-Thread-Pool und führt etwas Parallelität ein. Für Dinge wie Netzwerk-Clients sind weitere Optimierungen nötig
  • Green Threads: Nutzt asynchrone Systemaufrufe wie Linux io_uring, um mehrere Green Threads (leichtgewichtige Threads) auf OS-Threads zu verarbeiten. Benötigt Plattformunterstützung (zunächst x86_64 Linux)
  • Stacklose Coroutines: Coroutines auf Basis einer Zustandsmaschine ohne explizit benötigten Stack. Gedacht für Kompatibilität mit bestimmten Plattformen wie WASM. Erfordert die Wiedereinführung einer Coroutine-Konvention im Zig-Compiler

Designziele

Code-Wiederverwendung

Das größte Problem bei asynchronem I/O ist die Code-Wiederverwendung; in anderen Sprachen existieren Blocking- und Async-Funktionen getrennt, wodurch Code auseinanderfällt. Zigs Ansatz bedeutet:

  • Eine einzige Bibliothek unterstützt sowohl synchronen als auch asynchronen Modus effektiv
  • async/await beseitigt das Phänomen des „Function Coloring“, und über das Io-System bleibt man auch zur Laufzeit nicht an ein bestimmtes Ausführungsmodell gebunden

Damit wird das Problem des Function Coloring letztlich vollständig gelöst

Optimierung

  • Die neue Io-Schnittstelle wird nicht generisch, sondern über virtuelle Aufrufe auf Basis einer vtable implementiert
  • Virtuelle Aufrufe reduzieren Code-Bloat, verursachen zur Laufzeit aber einen kleinen Overhead. In optimierten Builds ist bei nur einer Io-Implementierung Devirtualisierung möglich
  • Wenn mehrere Io-Implementierungen verwendet werden, bleiben die virtuellen Aufrufe erhalten (um Codeduplikation zu vermeiden)

Buffering-Strategie

  • Bisher war Buffering Aufgabe jeder Implementierung (reader/writer), nun erfolgt es auf Ebene der Reader- und Writer-Schnittstellen
  • Außer beim Flush des Buffers muss der Pfad nicht über virtuelle Aufrufe gehen, was Optimierungen erleichtert

Semantische I/O-Operationen

Die Writer-Schnittstelle bietet zwei neue Primitive für gezielte Optimierungsoperationen.

  • sendFile: Inspiriert von POSIX sendfile; der Datentransfer zwischen File Descriptors erfolgt im Kernel, wodurch Speicherkopien minimiert werden
  • drain: Unterstützt vectorized write + splatting. Mehrere Datensegmente können gesammelt übertragen und in einen writev-Systemaufruf umgewandelt werden. Über den splat-Parameter lässt sich das letzte Element wiederholt verwenden (nützlich z. B. in komprimierten Streams)

Roadmap

Ein Teil dieser Änderungen wird ab Zig 0.15.0 eingeführt, für die vollständige Einführung ist jedoch eine umfassende Umgestaltung der Bibliothek nötig, sodass auf ein späteres Release gewartet werden muss. Wichtige Module wie SSL/TLS sowie HTTP-Server/-Clients sollen ebenfalls auf Basis des neuen Io-Systems neu entworfen werden.

FAQ

F: Zig ist doch eine Low-Level-Sprache – warum ist async wichtig?

  • Zig zielt auf Robustheit, Optimierung und Wiederverwendbarkeit
  • Durch die Standardisierung von Non-Blocking-I/O können auch andere Bibliotheken und Drittanbieter-Code auf die gesamte I/O-Strategie abgestimmt werden, was Anpassbarkeit und Wiederverwendbarkeit erhöht

F: Müssen Paketautoren async jetzt in ihrem gesamten Code verwenden?

  • Nein. Nicht jeder Code muss Konkurrenz ausdrücken
  • Auch gewöhnlicher sequenzieller Code funktioniert entsprechend der vom Nutzer gewählten I/O-Strategie

F: Funktioniert jeder beliebige Ausführungsmodus automatisch korrekt, solange man ihn nur einsteckt?

  • Meistens ja
  • Allerdings führen Programmierfehler im Code (z. B. wenn Anforderungen an gleichzeitige Aufgaben nicht erfüllt werden) dazu, dass es nicht korrekt funktioniert

Anhand von Ausführungsbeispielen wird außerdem auf den Unterschied zwischen Asynchronität und Parallelität sowie auf die Notwendigkeit einer sauberen Gestaltung des Kontrollflusses hingewiesen.

Fazit

Mit der Einführung der neuen Io-Schnittstelle steigert Zig die Flexibilität bei der Wahl von Ein-/Ausgabestrategien, die Code-Wiederverwendbarkeit und das Optimierungspotenzial deutlich. Dadurch können Entwickler Strukturen für Konkurrenz und Parallelität klarer ausdrücken und zugleich effektiv auf verschiedene Plattformen und Ausführungsmodelle reagieren, ohne durch die Einschränkungen synchroner oder asynchroner Funktionsschreibweisen gebunden zu sein.

1 Kommentare

 
GN⁺ 2025-07-14
Hacker-News-Kommentare
  • Ich möchte noch einmal auf diesen Punkt hinweisen. Im Artikel wird sogar gesagt, Zig habe das Problem des Function Coloring vollständig gelöst, aber ich stimme dem nicht zu. Wenn man die fünf Regeln aus dem bekannten Text "What color is your function?" noch einmal betrachtet, gibt es in Zig zwar keine Unterscheidung wie async/sync oder rot/blau, aber letztlich existieren dennoch nur zwei Fälle: IO-Funktionen und Nicht-IO-Funktionen. Technisch wurde das Problem gelöst, dass sich die Art des Funktionsaufrufs je nach Farbe unterscheidet, aber Funktionen, die IO benötigen, müssen weiterhin IO als Argument übergeben bekommen, und Funktionen, die es nicht brauchen, eben nicht. Im Kern fühlt es sich also unverändert an. IO-Funktionen können nur aus IO-Funktionen heraus aufgerufen werden, und auch das entkommt dem Coloring-Problem nicht wirklich. Natürlich kann man auch einen neuen Executor weiterreichen, aber ob man das wirklich will, ist fraglich. In Rust geht Ähnliches ebenfalls. Auch dass farbige Funktionsaufrufe umständlich sind, bleibt gleich. Der Punkt, dass einige zentrale Bibliotheksfunktionen colored sind, trifft weder auf Zig noch auf Rust zu. Der Kern des Coloring-Problems ist, dass Funktionen, die Kontext benötigen, also etwa einen async-Executor, Auth oder einen Allocator, diesen Kontext beim Aufruf zwingend bekommen müssen. Dass Zig genau diesen Punkt wirklich gelöst hat, halte ich für schwer zu behaupten. Allerdings ist Zigs Abstraktion hier sehr gut, während Rust in diesem Bereich Schwächen hat. Das Problem des Function Coloring selbst bleibt aber bestehen

    • Der zentrale Unterschied zum typischen async Function Coloring ist, dass Zigs Io nicht einfach ein spezieller Wert für asynchrone Verarbeitung ist, sondern ein notwendiger Wert für jede Form von IO, etwa Datei lesen, schlafen oder Zeit abfragen. Io ist keine Eigenschaft einer Funktion, sondern ein gewöhnlicher Wert, der überall liegen kann. In der Praxis wirkt das Coloring-Problem dadurch tatsächlich gelöst. In den meisten Codebasen existiert IO ohnehin schon irgendwo im Scope, sodass nur wirklich rein berechnende Funktionen kein IO brauchen. Wenn eine Funktion plötzlich IO benötigt, kann sie es in den meisten Fällen direkt aus my_thing.io holen und verwenden. Anders als in Rust muss man nicht jeder Funktion einen Allocator mitgeben, daher ist das nicht so lästig. Wenn sich also ein Codepfad ändert und IO nötig wird, muss man die Änderung nicht durch jede Funktion propagieren, sondern kann es sofort verwenden. Grundsätzlich stimme ich zu, dass Function Coloring noch vorhanden ist, aber faktisch sind dadurch praktisch alle Funktionen async-colored, weshalb das praktische Problem fast verschwindet. Tatsächlich sehen Zig-Entwickler das explizite Weiterreichen eines Allocators auch nicht als lästiges Function Coloring an. Ich vermute, dass Io genauso wenig ein großes Problem sein wird

    • Ein wichtiger Punkt scheint mir hier zu fehlen. Wenn man Rust-Bibliotheken nutzt, muss man zwangsläufig Bedingungen wie async/await, tokio oder send+sync erfüllen, und in der Praxis ist eine sync-API in einer async-App oft unbrauchbar. Zigs Art, IO zu übergeben, löst dieses Problem dagegen grundlegend. Dadurch muss man sich nicht mit procedural macros oder erzwungenen Multiversionen abmühen, zumal dieser Ansatz das Problem von Bibliotheks-Multiversionen am Ende ohnehin nicht wirklich gut löst. Es gibt in Rust viele Diskussionen über das Mischen von async und sync, und unter folgendem Link wird das ebenfalls erklärt: https://nullderef.com/blog/rust-async-sync/. Hoffentlich gelingt es Zig künftig auch, cooperative scheduling, performantes async und Thread-per-Core-async gut zu lösen

    • Ich bin kein Experte für Kategorientheorie, aber wenn man diesen Weg des Kontextmanagements weitergeht, landet man am Ende bei der IO-Monade. Dieser Kontext kann implizit vorhanden sein, aber wenn man wirklich die Hilfe des Compilers bekommen will, muss er im System als konkrete Entität sichtbar werden. Und obwohl die Ambitionen von Systemprogrammiersprachen bisher immer wieder auf dem Friedhof von Async und Coroutines gelandet sind, gibt es Hoffnung für eine neue Generation darin, dass Andrew die IO-Monade gewissermaßen neu entdeckt und sauber umgesetzt hat. Reale Weltfunktionen haben Farben. Entweder man gibt klare Bewegungsregeln vor, oder man landet unweigerlich auf einem immer komplexeren Weg wie bei C++ co_await oder tokio. Ich denke, das ist genau ‘The Way’

    • Es gibt einen einfachen Trick, um alle Funktionen rot zu machen, oder blau

      var io: std.Io = undefined;
      
      pub fn main() !void {
        var impl = ...;
        io = impl.io();
      }
      

      Wenn man io als globale Variable benutzt, muss man sich keine Gedanken mehr über Coloring machen. Das ist natürlich ein Witz, aber sicher gibt es etwas Reibung dadurch, dass man das Io-Interface verwenden muss. Dennoch ist das im Kern etwas anderes als die reale Friktion, die bei async/await entsteht. Für mich liegt das Wesen des Function Coloring darin, dass die statische Farbzuweisung durch das async-Schlüsselwort die Wiederverwendung von Code unmöglich macht. In Zig bekommt eine Funktion in beiden Fällen, ob async oder nicht, IO als Argument, deshalb ist Coloring aus dieser Perspektive bedeutungslos. Zweitens zwingt async/await einen dazu, stacklose Coroutines zu verwenden, also Compiler-gesteuerte Stackwechsel, während Zigs neues IO-System intern async nutzen und trotzdem als Blocking IO arbeiten kann. Genau das ist für mich das eigentliche praktische Problem des Function Coloring

    • Auch Go leidet unter einem „subtilen Coloring“-Problem. Wenn man goroutines verwendet, muss man zur Behandlung von Abbrüchen stets einen context-Parameter weiterreichen, und viele Bibliotheksfunktionen verlangen ebenfalls context, wodurch der ganze Code davon durchdrungen wird. Technisch könnte man auf context verzichten, aber willkürlich context.Background zu übergeben, gilt nicht als empfohlene Praxis

  • Das Konzept von sans-io wurde in Rust und anderswo bereits diskutiert; als Referenz dienen https://www.firezone.dev/blog/sans-io, https://sans-io.readthedocs.io/ und https://news.ycombinator.com/item?id=40872020

    • Wenn eine Funktion IO-Methoden direkt aufruft, ist die Struktur von außen nicht von IO trennbar, daher würde ich das nur schwer sans-io nennen. Wie in den Links beschrieben, sollte bei byte-stream-basierten Protokollen die Implementierung nur Ein- und Ausgabepuffer behandeln, und die Stelle, die Daten aus dem Netzwerk erhält, muss sie dem Aufrufer wirklich direkt übergeben, damit es echtes sans-io ist. Für die Ausgabe kann man entweder nur in einen Puffer schreiben oder bei einem Ereignis sofort einen Byte-Stream zurückgeben. Welche Rückgabeform man wählt, ist eine Implementierungsfrage, aber interne Puffer sind nützlich für Situationen, in denen automatische Antworten nötig sind. Entscheidend ist die Struktur ohne direktes IO
  • Ich denke, das Problem des Function Coloring besteht darin, dass man am Ende entweder auf dem Stack arbeitet oder den Stack unwindet, und eines von beidem bleibt immer übrig. Zig behauptet, das Coloring-Problem gelöst zu haben, erlaubt aber in der IO-Implementierung weiterhin blocking/thread pool/green thread. Solches Blocking IO war aber von vornherein nicht das eigentliche Problem. Wenn man sich an die Konvention hält, keinen globalen Zustand zu verwenden, ist so etwas in fast jeder Sprache möglich. Stacklose Coroutines sind noch nicht implementiert; es wirkt ein wenig wie „es fehlen nur noch die letzten Teile“. Wenn man wirklich universelle Funktionsaufrufe will, gibt es meiner Meinung nach zwei Wege

    • Alle Funktionen async machen und über ein Argument steuern, ob sie synchron ausgeführt werden sollen oder nicht (mit Performanceverlust)

    • Jede Funktion zweimal kompilieren und je nach Situation passend aufrufen (mit größerem Code und Schwierigkeiten bei Function Pointern)

      • Ich gehöre nicht zum Kernteam, aber soweit ich gehört habe, ist geplant, genau diese Lösung anzuwenden, also echte Coroutines auf Basis von Stack-Jumping einzufügen, sobald Nutzer und praktische Anwender semiblocking-Implementierungen ausreichend ausprobiert haben und die API stabilisiert ist. Der aktuelle Coroutine-State-Machine-Compiler von LLVM hat das Problem, von libc oder malloc abhängig zu sein. Weil Zigs neues io-Interface userland-async/await unterstützt, wird die Migration einfach und das Debugging angenehm bleiben, selbst wenn später eine saubere Frame-Jumping-Lösung kommt. Falls sich Coroutines als schwierig erweisen, ist die io-API außerdem so angelegt, dass sie mit kleineren Anpassungen bestehen kann, ohne stacklose Coroutines zu überstürzen

      • ValueTask<T> in C#/.NET erfüllt eine ähnliche Rolle. Wenn etwas synchron abgeschlossen wird, entsteht kein Overhead, und nur bei Bedarf wird daraus ein Task<T>. Im Code verwendet man gewöhnlich einfach await, und zur Laufzeit oder beim Kompilieren entscheidet das Runtime-System oder der Compiler selbst, ob synchron oder asynchron gearbeitet wird

  • Ich mag Zig, aber es macht mich etwas skeptisch, dass der Fokus auf green threads, also Fibers bzw. stackful coroutines, liegt. Rust hat vor 1.0 ein ähnliches Runtime-Trait aus Performancegründen verworfen. Tatsächlich haben OS, Sprachen und Bibliotheken die Nachteile dieses Ansatzes schon mehrfach gelernt, und dazu gibt es auch Material: https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1364r0.pdf. Fibers galten in den 90ern als skalierbare Form der nebenläufigen Verarbeitung, werden heute aber wegen stackloser Coroutines sowie der Fortschritte bei OS und Hardware nicht mehr empfohlen. Wenn es so weitergeht, wird Zig ähnlich wie Go an Performancegrenzen stoßen und schwerlich ein echter Performance-Konkurrent sein. Ich hoffe, std.fs bleibt für Fälle erhalten, in denen Performance wichtig ist

    • Der Eindruck, wir würden bei green threads, also Fibers, „all-in“ gehen, ist ein Missverständnis. Im verlinkten Artikel des OP wird ausdrücklich erwähnt, dass eine Implementierung auf Basis stackloser Coroutines erwartet wird, und es gibt auch einen entsprechenden Vorschlag: https://github.com/ziglang/zig/issues/23446. Performance ist wichtig, und wenn Fibers in der Praxis leistungsmäßig hinter den Erwartungen zurückbleiben, werden sie sich ohnehin nicht allgemein durchsetzen. Nichts von dem, was in diesem Artikel diskutiert wird, verhindert, dass stacklose Coroutines die Standardimplementierung von Io werden

    • Ich bin skeptisch gegenüber der Behauptung, green threads seien leistungsschwach. Führende Plattformen für hochgradig nebenläufige Server wie Go, Erlang und Java verwenden green threads oder bewegen sich in diese Richtung. Für Sprachen auf niedrigerem Niveau wie Rust könnten green threads wegen der Kompatibilität mit C FFI ungeeignet sein, aber dass Performance an sich immer das Problem sei, lässt sich daraus nicht zwingend ableiten

    • Da es nur eine von mehreren Optionen ist, würde ich das nicht als „all-in“ bezeichnen. Welche Implementierung gewählt wird, entscheidet die ausführbare Datei, nicht der Bibliothekscode

    • Zig zielt auf einen ähnlichen Effekt wie Rust mit seiner Entscheidung, green threads zu entfernen und durch ein async-Runtime-Modell zu ersetzen. Der Kern ist die offizielle Intuition „async=IO, IO=async“. Rust bietet ein pluggable async runtime wie tokio, Zig bietet ein pluggable IO runtime. Die Richtung ist letztlich, das Runtime-System aus der Sprache herauszunehmen, es in den User-Space zu verlagern und dabei alle dasselbe gemeinsame Interface nutzen zu lassen

    • Das Papier (P1364R0) war umstritten, und ich halte es für argumentativ motiviert, um einen bestimmten Ansatz zu verdrängen. Als weiteres Diskussionsmaterial kann man auch https://old.reddit.com/r/cpp/comments/1jwlur9/stackful_coroutines_faster_than_stackless/, https://old.reddit.com/r/programming/comments/dgfxde/fibers_arent_useful_for_much_any_more/f3bmpww/ ansehen

  • Es wirkt etwas seltsam, dass in einer Systemsprache wie Zig sogar bei alltäglichen Standard-IO-Operationen Runtime-Polymorphismus erzwungen wird. In den meisten realen Fällen kann die IO-Implementierung statisch feststehen, daher frage ich mich, warum man dafür Runtime-Overhead in Kauf nehmen soll

    • Ich denke, der Overhead durch dynamischen Dispatch ist bei IO in der Praxis fast immer vernachlässigbar. Das hängt zwar vom IO-Ziel ab, aber meistens ist IO ohnehin nicht CPU-limitiert. Daher heißt es ja auch IO-bound

    • Auf die Frage „Warum allen Runtime-Overhead aufzwingen?“ würde ich sagen, dass der Compiler in Systemen, die ohnehin nur eine Art von io verwenden, vermutlich darauf abzielt, die Kosten der doppelten Indirektion wegzuoptimieren. Und IO hat sowieso andere Bottlenecks, deshalb fällt eine zusätzliche Indirektion kaum ins Gewicht

    • In Zig wird traditionell stärker auf Binärgröße geachtet. Beim Allocator gibt es denselben Trade-off: ArrayListUnmanaged ist zum Beispiel nicht generisch über den Allocator, daher fällt bei jeder Allokation dynamischer Dispatch an. In der Praxis werden diese indirekten Aufrufkosten von Datei-Allokation oder Schreibkosten aber weit übertroffen. Diese Fixierung auf Binärgröße ist sehr Zig-typisch. Nebenbei gesagt ist devirtualization, also die Optimierung dynamischer Aufrufe zu statischen, ein Mythos

    • Runtime-Polymorphismus an sich ist nicht grundsätzlich schlecht. Solange es keine Situation wie einen tight loop mit zusätzlichem Branch oder fehlende Inline-Optimierung gibt, ist das kein Problemfall

  • Es gefällt mir nicht besonders, dass der neue io-Parameter überall sichtbar wird, aber ich mag sehr, dass sich damit verschiedene Implementierungen, etwa threadbasiert oder fiberbasiert, leicht nutzen lassen und dem Nutzer keine bestimmte Implementierung aufgezwungen wird, ähnlich wie beim Allocator-Interface. Insgesamt ist das eine deutliche Verbesserung, und wenn unter den verschiedenen stdlib-Implementierungen auch eine synchrone/blockierende IO-Implementierung ohne zusätzlichen Overhead bereitsteht, würde das perfekt zur Zig-Philosophie passen, dass man nicht für etwas bezahlt, das man nicht benutzt

    • Ist „nicht für etwas bezahlen, das man nicht benutzt“ hier wirklich möglich? Außer bei sehr kleinen Teams mit extrem strenger Disziplin wird es am Ende doch jemand anderes benutzen, und dann trage ich die Kosten mit. Außerdem scheint das ständige Weiterreichen von io lästiger zu sein als dort einfach direkt aufzurufen, wo es gebraucht wird
  • In Zig drückt io.async Asynchronität aus, also dass die Reihenfolge von Operationen nicht garantiert sein muss, auch wenn das Ergebnis korrekt bleibt, aber nicht Nebenläufigkeit. Genau diese Trennung zwischen der Bedeutung von async und der Bedeutung von io-Aufrufen ist der Kern. Ich halte dieses Design für sehr clever

  • Mir gefällt, dass sich durch das IO-Interface ein VFS, also Virtual File System, auf Sprachebene bauen lässt

    • Beim Blick auf den Beispielcode dachte ich aus Sicherheitsperspektive sofort daran, dass sich damit vielleicht auch capability-basierte Sicherheit umsetzen ließe, etwa indem man einer Bibliothek eine io-Instanz übergibt, die nur Lesezugriff unterhalb eines bestimmten Verzeichnisses erlaubt. Siehe auch https://news.ycombinator.com/item?id=44549430
  • Ich habe zum Lernen von Zig einmal einen einfachen SSH-Server gebaut. Durch diese neue IO-/Event-Loop-Struktur konnte ich den Ablauf des Codes deutlich leichter verstehen. Danke an Andy

    • Mich würde interessieren, welcher Aspekt des neuen Designs konkret dazu beigetragen hat, Event Loop und io leichter verständlich zu machen
  • Der Text ist hervorragend geschrieben, und ich fand ihn äußerst interessant. Besonders gespannt bin ich auf die Implikationen für WebAssembly. Dass man WASI auch im Userspace nutzen kann und zusätzlich Bring Your Own IO möglich ist, finde ich wirklich spannend