4 poin oleh GN⁺ 2025-12-04 | 1 komentar | Bagikan ke WhatsApp
  • Bahasa Zig memperkenalkan model baru berbasis antarmuka Io untuk mengurangi kompleksitas desain I/O asinkron yang ada
  • Model ini mempertahankan struktur fungsi yang sama tanpa membedakan kode sinkron dan asinkron, serta menyediakan dua implementasi: Io.Threaded dan Io.Evented
  • Io.Threaded pada dasarnya menjalankan eksekusi sinkron, sedangkan Io.Evented melakukan eksekusi asinkron berbasis event loop
  • Pengembang dapat mengontrol eksekusi paralel melalui fungsi async() dan concurrent(), sehingga optimasi performa dimungkinkan tanpa mengubah kode
  • Pendekatan ini menyelesaikan masalah function coloring sambil tetap mempertahankan kesederhanaan dan kemampuan kontrol khas Zig untuk meraih performa asinkron

Perubahan Desain Asinkron di Zig

  • Zig mencari pendekatan baru karena desain asinkron sebelumnya kurang selaras dengan filosofi minimalisme bahasa tersebut
    • Desain lama memiliki tingkat integrasi yang rendah dengan fitur lain
    • Model baru memungkinkan I/O sinkron dan asinkron ditangani dengan struktur kode yang sama
  • Desain baru berpusat pada antarmuka generik Io
    • Semua fungsi I/O dijalankan dengan menerima instance Io sebagai parameter
    • Strukturnya mirip dengan antarmuka Allocator, sehingga I/O dapat dikendalikan dengan cara yang serupa seperti alokasi memori

Struktur Antarmuka Io

  • Pustaka standar menyertakan dua implementasi dasar
    • Io.Threaded: pada dasarnya eksekusi sinkron, dengan pemrosesan paralel berbasis thread bila diperlukan
    • Io.Evented: eksekusi asinkron berbasis event loop (menggunakan io_uring, kqueue, dan lain-lain)
    Iklan
  • Pengguna juga dapat menulis implementasi Io baru sendiri, sehingga bisa mengontrol cara eksekusi dengan lebih rinci

Contoh Kode dan Cara Kerjanya

  • Fungsi contoh saveFile() membuat file, menulis isinya, lalu menutupnya
    • Saat menggunakan Io.Threaded, fungsi ini berjalan lewat system call biasa
    • Saat menggunakan Io.Evented, fungsi ini dijalankan melalui backend asinkron
    • Dalam kedua kasus, penyelesaian pekerjaan dijamin pada saat pemanggilan writeAll()
  • Kode yang sama berjalan identik baik di lingkungan sinkron maupun asinkron
    • Penulis library tidak perlu memikirkan metode eksekusinya

Eksekusi Paralel dan async() / concurrent()

  • Fungsi async() meminta eksekusi asinkron, tetapi pada Io.Threaded bisa saja dijalankan segera
    • Pada Io.Evented, dua file benar-benar dapat disimpan secara bersamaan lewat eksekusi asinkron
  • Fungsi concurrent() digunakan ketika eksekusi paralel yang nyata memang diperlukan
    • Io.Threaded memanfaatkan thread pool
    • Io.Evented menanganinya dengan cara yang sama seperti async()
    Iklan
  • Pemilihan fungsi yang salah (async alih-alih concurrent) dianggap sebagai bug, dan tidak bisa dicegah di tingkat bahasa

Gaya Kode dan Integrasi Bahasa

  • Gaya kode Zig biasa tetap dipertahankan tanpa sintaks khusus untuk asinkron
    • Sintaks alur kontrol yang ada seperti try, defer, dan lainnya tetap digunakan apa adanya
    • Andrew Kelley menyebutnya “terbaca seperti kode Zig standar”
  • Sebagai contoh, ditunjukkan implementasi lookup DNS asinkron
    • Tidak seperti getaddrinfo(), implementasi ini hanya mengembalikan respons sukses pertama dan membatalkan permintaan lainnya

Rencana Ke Depan dan Status Pengembangan

  • Io.Evented masih berada pada tahap eksperimental, dan belum mendukung sebagian OS
  • Implementasi Io yang kompatibel dengan WebAssembly sedang direncanakan dan memerlukan pengembangan fitur terkait
  • Ada 24 item pekerjaan lanjutan terkait Io, dan sebagian besar masih belum selesai
  • Zig masih belum mencapai versi 1.0, dan I/O asinkron serta pembuatan kode native adalah tugas besar yang masih tersisa
  • Dengan desain ini, diharapkan frekuensi penulisan ulang kode akibat perubahan antarmuka I/O akan berkurang
Iklan

Ringkasan Diskusi Komunitas

  • Sejumlah komentar menilai pendekatan Zig lebih sederhana dan fleksibel dibanding model async/await Rust
    • Rust menjadi lebih kompleks ketika beberapa executor digunakan bersamaan
    • Zig melalui antarmuka Io membuka kemungkinan koeksistensi banyak executor
  • Sebagian pihak menyoroti bahwa kodenya bisa menjadi agak lebih bertele-tele
    • Namun, desain API yang eksplisit meningkatkan kemampuan kontrol atas keamanan, performa, dan pengujian
  • Diskusi teknis juga berlanjut mengenai perbedaan eksekusi asinkron dan berbasis thread, serta pendekatan implementasi stackful vs stackless coroutine
  • Io di Zig diimplementasikan sebagai perluasan pustaka standar tanpa perlakuan khusus di tingkat bahasa
    • Ke depan, fitur stackless coroutine akan ditambahkan

Kesimpulan

  • Model asinkron baru Zig bertujuan menyeimbangkan kesederhanaan bahasa dan I/O berperforma tinggi
  • Dengan menyelesaikan masalah function coloring, menyatukan kode sinkron dan asinkron, serta menyediakan struktur kontrol yang eksplisit, ini dinilai sebagai langkah kunci menuju stabilisasi Zig 1.0

1 komentar

 
GN⁺ 2025-12-04
Komentar Hacker News
  • Secara keseluruhan, tulisan ini akurat dan diteliti dengan baik
    Hanya saja ada beberapa koreksi kecil.
    Pada instance Io.Threaded, async() sebenarnya tidak berjalan secara asinkron dan dieksekusi langsung. Namun std.Io.Threaded secara default menggunakan thread pool untuk mendistribusikan pekerjaan asinkron.
    Tetapi, jika diinisialisasi dengan init_single_threaded, perilakunya akan sesuai dengan yang dijelaskan dalam artikel.
    Satu lagi, dulu ada fungsi bernama asyncConcurrent(), tetapi sekarang namanya sudah diubah menjadi concurrent()

    • Saya Daroc. Saya sudah menerapkan dua koreksi pada artikel berdasarkan masukan ini.
      Jika ingin memberi umpan balik di masa mendatang, kirim email ke lwn@lwn.net.
      Terima kasih atas usulan perbaikannya dan atas pekerjaan terkait Zig
    • Saya punya pertanyaan untuk Andrew.
      Saya penasaran bug seperti apa yang bisa muncul jika asyncConcurrent() keliru dipakai di tempat yang seharusnya menggunakan async().
      Saya ingin tahu apakah, tergantung model IO-nya, ini bisa menjadi UB (undefined behavior), atau hanya sekadar kesalahan logika
    • Hal yang bagus dari concurrent() adalah ia meningkatkan keterbacaan dan daya ekspresif kode, sehingga jelas menunjukkan, “kode ini memang harus dijalankan secara paralel”
  • Menurut saya desain ini cukup masuk akal.
    Namun penjelasan Zig membingungkan.
    Mereka menekankan bahwa masalah function coloring sudah diselesaikan, tetapi pada dasarnya mereka hanya mendorong IO ke dalam effect type.
    Ini tetap mengharuskan pemanggil mempertahankan token, jadi masih merupakan bentuk pewarnaan juga.
    Menurut saya ini mirip dengan pendekatan penanganan async di Go

    • Jika hanya karena dipanggil dengan argumen berbeda lantas menjadi “fungsi berwarna”, maka semua fungsi berarti berwarna dan maknanya hilang ;)
      Model async-await Zig yang lama juga sebenarnya sudah menyelesaikan masalah coloring.
      Karena compiler otomatis membuat versi sinkron/asinkron sesuai konteks pemanggilan
    • Sebenarnya inti masalah function coloring adalah duplikasi jalur kode sinkron/asinkron.
      Zig menyelesaikannya secara praktis dengan dependency injection, dan itu sudah cukup memadai.
      Kompleksitas pemanggilan async memang tidak bisa dihindari, tetapi itu bagian yang tak terelakkan jika ingin kontrol yang presisi
    • io Zig bukan effect type yang menular.
      Anda bisa mendeklarasikan variabel io global dan memakainya dari mana saja (meskipun tentu tidak disarankan saat menulis library).
      Jika melihat tulisan What color is your function? yang merangkum lima syarat masalah function coloring, kemungkinan besar pendekatan Zig tidak memenuhi sebagian syarat itu (terutama 4 dan 5)
    • Pada praktiknya Zig tampaknya mewarnai semuanya sebagai async, lalu hanya membiarkan Anda memilih apakah akan memakai worker thread atau tidak.
      Namun pendekatan seperti ini bisa memicu masalah seperti deadlock.
      Sebagian kode tidak thread-safe, jadi coloring justru membantu
    • Dari sudut pandang pengembang Haskell, Zig terlihat seperti mengimplementasikan IO monad tanpa dukungan bahasa
  • Desain ini tampak sangat mirip dengan async di Scala.
    Di Scala, execution context diteruskan sebagai parameter implisit, sedangkan Zig menerimanya secara eksplisit.
    Dalam praktiknya, ini tidak jauh lebih baik daripada memakai thread dan queue secara langsung, dan pengelolaan execution context menimbulkan kompleksitas serta perilaku yang sulit diprediksi.
    Tim Zig tampaknya kurang memiliki pengalaman Scala sehingga menganggap pendekatan ini baru

    • Jika memakai thread OS secara langsung, Anda akan terbentur batas skalabilitas sesuai Little's law.
      JVM mengatasinya dengan virtual thread, tetapi bahasa tingkat rendah sulit mencapai efisiensi yang sama.
      Karena itu bahasa seperti Zig memerlukan solusi skalabilitas dengan pendekatan berbeda
    • Sebagai referensi, Anda bisa memahami konsep terkait lebih baik lewat ExecutionContext API milik Scala
  • Pada sistem async/await Zig yang lama, fungsi bisa melakukan suspend/resume.
    Dengan fitur ini saya ingin mencoba mengimplementasikan suspend/resume frame berbasis interrupt perangkat saat mengembangkan OS.
    Di sistem io baru, tampaknya saya harus mengimplementasikannya sendiri, dan itu agak disayangkan

    • Ada builtin tingkat rendah bernama @asyncSuspend dan @asyncResume.
      Io baru adalah abstraksi umum untuk mode sinkron, threaded, dan event-based, jadi mekanisme suspend tidak disertakan
    • Pada akhirnya ada kemungkinan suspend/resume akan diimplementasikan sebagai fungsi pustaka standar di user space.
      Jika melihat prototipe Io.Evented saat ini, ini juga bisa ditangani di library pihak ketiga berbasis stackless coroutine
    • Saya juga penasaran apakah suspend/resume bisa diimplementasikan hanya dengan satu thread pool
    • Saya juga ragu apa arti mengimplementasikan coroutine kooperatif sebagai async preemptive
  • Pada kode contoh dikatakan bahwa saat writeAll() mengembalikan nilai, pekerjaan sudah selesai,
    tetapi implementasi IO bisa beragam, jadi secara nyata penyelesaian seharusnya dijamin saat defer mulai.
    Jika tidak, perlu ada pelacakan dependensi antara createFile dan writeAll.
    Kalau begitu, pada akhirnya ini tampak tidak berbeda dari pemanggilan blocking.
    Selain itu, juga tidak jelas mengapa antarmuka ini dinamai IO.
    Pada praktiknya ini lebih dekat ke abstraksi “menjalankan sesuatu di konteks lain”
    Dokumen terkait: std.Io

  • Contoh berikut menarik

    var a_future = io.async(saveFile, .{io, data, "saveA.txt"});
    var b_future = io.async(saveFile, .{io, data, "saveB.txt"});
    const a_result = a_future.await(io);
    const b_result = b_future.await(io);
    

    Di Rust atau Python, coroutine tidak akan berjalan jika tidak di-await.
    Sebaliknya, jika dalam contoh Zig io.async bergerak sendiri, maka ini mirip dengan pembuatan task.
    Ini adalah desain yang valid, tetapi bukan arah yang dipilih bahasa lain

    • C# juga bekerja mirip. Fungsi async berjalan di thread pemanggil sampai sebelum yield
    • Di Zig juga sama, eksekusi baru dijamin jika .await(io) dipanggil.
      Apakah ia langsung berjalan atau dimasukkan ke antrean thread pool bergantung pada implementasi runtime Io
    • Pada praktiknya, eksekusi berjalan saat await.
      Dalam evented io, kedua pekerjaan bisa dieksekusi secara selang-seling (interleaved), dan dalam threaded io bisa berjalan di latar belakang.
      Jadi tidak ada “task yang diam-diam berjalan entah di mana”
    • JavaScript juga bekerja seperti ini
  • Sebagai orang yang memakai Go setiap hari, saya merasa Io Zig memperbaiki berbagai kekurangan di Go.
    Namun saya penasaran apakah Zig punya konsep channel.
    Di Go ada keyword select, tetapi saya selalu merasa sayang karena itu tidak bisa dipakai pada socket

    • Ada yang menyoroti bahwa membungkus semua IO dalam channel biayanya besar.
      Channel di Go punya overhead puluhan siklus, jadi tidak efisien untuk IO berukuran kecil.
      Sebaliknya, itu berguna untuk pemindahan data berukuran besar atau sinkronisasi many-to-many
    • Zig memiliki std.Io.Queue yang mirip dengan channel di Go.
      Pernyataan select juga bisa diimplementasikan secara serupa, meskipun secara sintaksis kurang ergonomis.
      Sebagai gantinya, ini bisa berjalan di berbagai runtime IO tanpa GC
    • Saya ingin bertanya apakah Anda pernah memakai bahasa Odin. Ini adalah “better C” yang mendapat lebih banyak inspirasi dari Go dibanding Zig
    • Saya suka bahwa pendekatan ini tidak memaksa fungsi berwarna seperti async/await di C#.
      Menurut saya pendekatan Zig yang “tanpa warna” jauh lebih baik
    • Mengira model concurrency Go itu istimewa adalah masalah.
      Goroutine hanyalah green thread, dan channel hanyalah queue yang thread-safe, dan Zig juga sudah menyediakannya di pustaka standar
  • Versi async dari Io di Zig tampak hampir sama dengan pendekatan Go.
    Bedanya, di Go biaya alokasi stack saat memanggil library C besar, dan syscall langsung punya masalah kompatibilitas lintas platform.
    Zig tampaknya membuat ini bisa dikonfigurasi, sehingga berbagai trade-off bisa dipilih tanpa mengubah kode

  • Async IO baru ini sangat bagus untuk contoh sederhana, tetapi pada IO kompleks tingkat server mungkin ada batasannya.
    Saya sudah membuka isu terkait di GitHub

  • Masalah intinya adalah perancang bahasa atau library harus menyediakan cara untuk menghubungkan konteks eksekusi yang berbeda (sinkron/asinkron).
    Untuk itu diperlukan pendekatan membungkus konteks dalam FSM (finite state machine), lalu menyediakan saluran komunikasi di antara keduanya
    Tulisan terkait: Function colors represent different execution contexts