1 poin oleh GN⁺ 2026-05-06 | 2 komentar | Bagikan ke WhatsApp
  • Async Rust memungkinkan kode yang independen dari executor berjalan baik di server maupun mikrokontroler, tetapi state machine yang dibuat compiler menyebabkan pembengkakan ukuran biner yang sangat terlihat, terutama di embedded
  • Bahkan contoh sederhana seperti bar() dengan 2 titik await menghasilkan 360 baris MIR serta state Unresumed, Returned, Panicked, Suspend0, Suspend1, sedangkan versi sinkron hanya membutuhkan 23 baris
  • Jika future yang sudah selesai di-poll lagi diubah agar mengembalikan Poll::Pending alih-alih panic, kontrak tetap bisa dipenuhi tanpa perilaku unsafe, dan eksperimen menunjukkan ukuran biner firmware embedded berkurang 2%~5%
  • Bahkan async { 5 } tanpa await saat ini tetap membuat state machine dengan 3 state dasar, tetapi jika dioptimalkan agar selalu mengembalikan Poll::Ready(5), ukuran biner embedded berkurang 0.2%
  • Project Goal yang diusulkan bertujuan mendorong penghapusan panic setelah selesai di mode rilis, penghapusan state machine untuk async block tanpa await, inline future dengan satu await, dan penggabungan state yang identik di compiler

Masalah pembengkakan di level compiler pada Async Rust

  • Async Rust memungkinkan kode yang independen dari executor berjalan sekaligus di server dan mikrokontroler, tetapi pada mikrokontroler kecil peningkatan ukuran biner sangat terasa
  • Blog Rust memperkenalkan async/await sebagai abstraksi tanpa biaya, tetapi pada praktiknya async menghasilkan banyak pembengkakan (bloat), dan meski masalah yang sama juga ada di desktop dan server, hal itu kurang terlihat karena memori dan sumber daya komputasinya lebih besar
  • Setelah ada cara mengakali agar tidak terlalu membengkak saat menulis kode async, kini diajukan Project Goal untuk menyelesaikan masalah ini di compiler
  • Masalah future yang menjadi terlalu besar dan terlalu banyak disalin tidak termasuk dalam cakupan

Struktur future yang dihasilkan

  • Kode contoh memperlihatkan foo() mengembalikan async { 5 }, dan bar() menjalankan foo().await + foo().await
  • bar memiliki 2 titik await, sehingga state machine minimal membutuhkan 2 state, tetapi pada kenyataannya lebih banyak state yang dibuat
  • Compiler Rust dapat membuang MIR di berbagai pass, dan pass coroutine_resume adalah pass MIR khusus async terakhir
    • Async masih tersisa di MIR tetapi tidak lagi ada di LLVM IR, jadi proses perubahan async menjadi state machine terjadi di pass MIR
  • Fungsi bar menghasilkan 360 baris MIR, sedangkan versi sinkron hanya memakai 23 baris
  • CoroutineLayout yang dikeluarkan compiler pada dasarnya adalah kumpulan state berbentuk enum
    • Unresumed: state awal
    • Returned: state selesai
    • Panicked: state setelah panic
    • Suspend0: titik await pertama, menyimpan future foo
    • Suspend1: titik await kedua, menyimpan hasil pertama dan future foo kedua
  • Future::poll adalah fungsi yang aman, sehingga memanggilnya lagi setelah future selesai tidak boleh menyebabkan UB
    • Saat ini setelah Suspend1, ia mengembalikan Ready lalu mengubah future ke state Returned
    • Jika di-poll lagi pada state ini, panic akan terjadi
  • State Panicked tampaknya ada agar setelah fungsi async panic dan ditangkap oleh catch_unwind, future tersebut tidak bisa di-poll lagi
    • Setelah panic, future bisa berada dalam state yang tidak lengkap, sehingga mem-poll lagi dapat berujung pada UB
    • Mekanisme ini sangat mirip dengan mutex poisoning
    • Interpretasi terhadap state Panicked ini tidak didukung dokumentasi yang benar-benar pasti, jadi tingkat keyakinannya sekitar 90%

Apakah poll setelah selesai memang harus panic

  • Future dalam state Returned saat ini akan panic, tetapi sebenarnya itu tidak wajib
    • Syarat yang penting hanyalah tidak menimbulkan UB
  • Panic relatif mahal dan menambahkan jalur dengan efek samping yang sulit dihilangkan lewat optimasi
  • Jika future yang sudah selesai di-poll lagi mengembalikan Poll::Pending, kontrak tipe Future tetap terpenuhi tanpa perilaku unsafe
  • Saat compiler dimodifikasi untuk menguji pendekatan ini, firmware embedded async menunjukkan penurunan ukuran biner 2%~5%
  • Perilaku ini diusulkan tersedia sebagai sakelar seperti overflow-checks = false pada integer overflow
    • Di build debug, panic tetap dipertahankan agar perilaku yang salah langsung terlihat
    • Di build rilis, future yang lebih kecil bisa didapatkan
  • Saat menggunakan panic=abort, ada kemungkinan state Panicked itu sendiri dapat dihapus, tetapi dampaknya masih perlu ditinjau lebih lanjut

State machine tetap dibuat meski tidak ada await

  • foo() hanya mengembalikan async { 5 }, sehingga bentuk implementasi manual yang paling optimal adalah future tanpa state yang selalu mengembalikan Poll::Ready(5)
  • Namun MIR yang dihasilkan compiler masih memiliki 3 state dasar: Unresumed, Returned, Panicked
    • Saat di-poll, discriminant dari state saat ini diperiksa lalu bercabang
    • Jika di-poll lagi setelah selesai, panic terjadi dengan assert `async fn` resumed after completion
  • Dalam kasus ini, compiler bisa dioptimalkan agar tidak membuat state machine dan cukup selalu mengembalikan Poll::Ready(5)
  • Saat ini diterapkan secara eksperimental di compiler, ukuran biner embedded berkurang 0.2%
    • Penghematannya memang tidak besar, tetapi optimasinya sederhana sehingga tetap berpotensi layak diterapkan
  • Optimasi ini sedikit mengubah perilaku, tetapi yang terdampak hanyalah executor yang tidak mematuhi kontrak
    • Compiler saat ini panic pada poll berikutnya
    • Setelah optimasi, future akan selalu mengembalikan Ready

LLVM saja tidak cukup

  • Meski keluaran MIR tidak efisien, kadang LLVM bisa membereskannya, tetapi syaratnya terbatas
    • Future harus cukup sederhana
    • Harus memakai opt-level=3
  • Saat future menjadi lebih kompleks, LLVM tidak mampu menghapus semuanya, dan dalam kode Async Rust yang idiomatis future cenderung bertingkat dalam, sehingga kompleksitas cepat membesar
  • Di lingkungan seperti embedded atau wasm yang sering mengutamakan optimasi ukuran, LLVM tidak bisa mengoptimalkan semuanya
  • Contoh Godbolt: https://godbolt.org/z/58ahb3nne
    • Dari assembly yang dihasilkan, LLVM mengetahui bahwa foo mengembalikan 5, tetapi tidak bisa mengoptimalkan jawaban bar menjadi 10
    • Panggilan ke fungsi poll milik foo juga masih tersisa
    • Penyebabnya adalah adanya jalur panic potensial yang tidak bisa dipahami compiler sepenuhnya
    • LLVM tidak tahu bahwa foo pada kenyataannya hanya dipanggil sekali dan tidak panic
  • Jika cabang panic di IR dikomentari, optimasinya menjadi lebih baik: https://godbolt.org/z/38KqjsY8E
  • Daripada berharap LLVM melakukan optimasi lanjutan setelahnya, compiler seharusnya memberi input yang lebih baik ke LLVM

Inline future tidak berjalan dengan baik

  • Inline penting karena memungkinkan pass optimasi berikutnya, tetapi future Rust yang dihasilkan saat ini tidak di-inline pada tahap awal
  • Setelah tiap future mendapat implementasinya, LLVM dan linker memang mendapat kesempatan untuk melakukan inline, tetapi akibat masalah sebelumnya tahap itu sudah terlambat
  • Peluang inline paling langsung adalah ketika bar() hanya melakukan foo(blah).await
    • Pola ini sering muncul saat membuat abstraksi dengan trait
    • Compiler saat ini membuat state machine untuk bar lalu memanggil state machine foo di dalamnya
    • Secara lebih efisien, bar bisa langsung menjadi future foo itu sendiri
  • Jika ada preamble dan postamble, kasusnya lebih kompleks
    • Contoh: bar(input) membuat blah dari input > 10, lalu menjalankan foo(blah).await dan menerapkan * 2 pada hasilnya
    • Ini umum saat mengubah fungsi async ke signature lain, terutama dalam implementasi trait
  • Bentuk bar seperti ini juga tidak memerlukan state async miliknya sendiri
    • Tidak ada data yang perlu dipertahankan melewati satu titik await selain nilai yang sudah ditangkap oleh foo
    • Namun bar tidak bisa begitu saja menjadi foo itu sendiri, meski sebagian besar state dapat bergantung pada foo
  • Dalam implementasi manual, BarFut dapat memiliki state Unresumed { input } dan Inlined { foo: FooFut }
    • Pada poll pertama, preamble dijalankan untuk membuat foo(blah) lalu diubah ke state Inlined
    • Setelah itu, hasil foo.poll(cx) diteruskan dengan menerapkan postamble
  • Jika kode hingga sebelum titik await pertama bisa dieksekusi lebih awal, state Unresumed juga bisa dihapus, tetapi hal itu tidak bisa diubah karena ada jaminan bahwa future tidak melakukan apa pun sebelum di-poll
  • Jika properti future yang sedang di-poll bisa diinspeksi, optimasi inline tambahan dapat dimungkinkan
    • Misalnya, jika diketahui bahwa future selalu mengembalikan ready pada poll pertama, future pemanggil tidak perlu membuat state untuk titik await tersebut
    • Jika optimasi semacam ini diterapkan secara rekursif, banyak future bisa dilipat menjadi state machine yang jauh lebih sederhana
  • Dalam struktur rustc saat ini, tampaknya tiap async block ditransformasikan secara terpisah dan data terkait tidak dipertahankan setelahnya, sehingga kueri seperti ini tidak memungkinkan
  • Inline future belum diuji secara eksperimental, tetapi diperkirakan sangat membantu ukuran biner dan performa

Menggabungkan state yang identik

  • Setiap titik await dalam async block menambah state baru pada state machine
  • Kode seperti berikut terasa alami, tetapi karena kedua cabang me-await fungsi async yang sama, akan muncul 2 state identik
    • CommandId::A => send_response(123).await
    • CommandId::B => send_response(456).await
  • Dalam kasus ini, CoroutineLayout memiliki _s0 dan _s1 yang masing-masing menyimpan tipe coroutine send_response yang sama, serta membuat 2 state: Suspend0 dan Suspend1
  • MIR fungsi ini memiliki 456 baris, dan banyak basic block pada dasarnya duplikat
  • Jika kode direfaktor manual agar lebih dulu menghitung nilai respons lalu hanya sekali memanggil send_response(response).await, state yang duplikat hilang
    • CommandId::A menjadi 123
    • CommandId::B menjadi 456
    • Setelah itu send_response(response).await
  • Setelah refaktor, CoroutineLayout hanya memiliki satu future yang disimpan dan hanya menyisakan satu state Suspend0
  • Panjang total MIR turun menjadi 302 baris, dan duplikasi hilang
  • Karena itu, pass optimasi untuk menemukan jalur kode dan state yang identik lalu menggabungkannya tampak berguna
    • Optimasi ini kemungkinan akan cocok digabungkan dengan pass inline future

Tautan eksperimen dan benchmark tambahan

Permintaan dukungan untuk Project Goal

  • Pekerjaan ini diajukan sebagai Project Goal agar bisa dikerjakan di compiler: https://rust-lang.github.io/rust-project-goals/2026/async-statemachine-optimisation.html
  • Tanpa pendanaan, sulit untuk mendorong banyak pekerjaan, sehingga dibutuhkan dukungan parsial atau penuh dari perusahaan atau organisasi yang akan mendapat manfaat dari pekerjaan ini
  • Kontaknya adalah dion@tweedegolf.com
  • Cakupan pekerjaan dan besaran pendanaan yang dibutuhkan bersifat fleksibel, tetapi diperkirakan €30k cukup untuk menyelesaikan seluruh atau sebagian besar pekerjaan

2 komentar

 
GN⁺ 2026-05-06
Komentar Hacker News
  • Saya setuju judulnya agak berlebihan, tapi isi tulisannya bagus dan poin utamanya tersampaikan dengan baik
    Saya belum cukup berpengalaman untuk punya pendapat kuat soal async Rust, tapi ada beberapa hal yang menonjol
    Hal baiknya adalah kita bisa punya runtime yang eksplisit. Alih-alih mencemari seluruh proyek menjadi async, default-nya bisa tetap sinkron dan runtime hanya dipakai di “batas” I/O
    Untuk proyek yang sedang saya kerjakan, pendekatan ini cocok, dan tampaknya cukup mirip dengan strategi yang diambil Zig untuk kode I/O. Dalam kasus ini, masalah warna fungsi juga sebagian besar teratasi, dan karena kami memang harus memisahkan dengan ketat kode I/O dan kode yang berpusat pada CPU, runtime I/O yang eksplisit terasa alami
    Hal buruknya adalah seluruh ekosistem tampak terlalu bergantung pada tokio. Mirip seperti GC di Java itu opsional tetapi pada praktiknya semua orang memakai runtime GC pihak ketiga yang sama, dan library apa pun yang Anda ambil memaksa runtime itu. Ketergantungan yang terpusat seperti ini tidak sehat

    • Tergantung konteksnya, memang bisa terlihat seperti seluruh ekosistem bergantung pada tokio, tetapi kalau melihat embedded Rust rasanya jadi lebih masuk akal
      Kebutuhan runtime async pada prosesor workstation dan pada lingkungan seperti RP2040 sangat berbeda. Meski begitu, karena backend bisa diganti, saat menulis kode I/O async untuk mikrokontroler ARM M0 kecil, dengan memakai embassy sebagai runtime yang berfokus pada embedded, tampilannya tetap hampir sama dengan kode yang dipakai di lingkungan lain
      Karena memakai trait dan antarmuka yang sama, kita bisa lebih sedikit memikirkan detail runtime. Dibanding memakai RTOS kecil atau membangun lingkungan async sendiri, ini cukup bagus
      Hal-hal yang dipelajari saat menulis kode async di embassy juga bisa dibawa ke area lain
    • Saya penasaran apa alternatifnya. Saya puas memakai tokio, tetapi bagus juga kalau orang lain memakai executor lain seperti smol, async-std, atau glommio
      Walaupun tokio bukan bagian dari standard library, ia dikelola dengan baik, jadi keadaan sekarang tampak baik-baik saja. Justru saya khawatir kalau masuk ke standard library, akan lebih sulit memakai executor lain, dan standard library juga bisa jadi lebih sulit dipindahkan ke platform lain
      Tentu saja, bisa jadi kekhawatiran ini tidak berdasar
    • Menarik bahwa Java disebut, karena Java juga secara historis mengalami masalah serupa
      Logging sekarang kurang lebih sudah tersatukan dengan slf4j, tetapi masih ada library yang memakai hal lain, dan utilitas umum dulu Apache Commons lalu sekarang banyak yang memakai Guava
      Untuk JSON, sebagian besar terkonsolidasi ke Jackson, tetapi Gson dan Simple-json juga masih umum, dan anotasi nullability juga belum pernah benar-benar diresmikan, bergerak dari distribusi tidak resmi JSR-305 ke checker framework, lalu belakangan ke JSpecify
      Elemen-elemen dasar seperti ini seharusnya disediakan oleh bahasa agar tidak terjadi fragmentasi dan menjamurnya standard library de facto
    • Ada banyak area di mana Anda bisa memakai async dan tetap memanfaatkan Rust tanpa bergantung pada tokio. Sebenarnya yang benar-benar terkunci ke tokio tampaknya lebih dekat ke sisi web/server
      Menulis library agar independen dari executor tidak terlalu sulit, tetapi butuh perhatian terus-menerus, dan itu bukan sesuatu yang selalu dijaga oleh sebagian besar komunitas
  • Tulisan yang luar biasa. Saya suka analisis optimisasi mendalam seperti ini, dan semoga tujuan proyeknya juga berjalan baik
    Saya kadang merasa compiler sering tidak mengerahkan banyak usaha untuk optimisasi kasus yang “sepele”
    Namun, judulnya terlalu dramatis dibanding isinya. Saya rasa saya tetap akan mengklik kalau judulnya “Async Rust Optimizations the Compiler Still Misses”

    • Judul itu dipilih karena memang faktanya begitu. Sejak async masuk sekitar 2019, tidak banyak yang benar-benar berubah
      Sekarang async bisa dipakai di trait dan closure, tetapi itu pembaruan sistem tipe, bukan perubahan pada mesin async itu sendiri. Waker juga sedikit lebih mudah ditangani, tetapi itu lebih dekat ke perbaikan di sisi std/core
      Setahu saya, orang-orang yang mendaratkan async Rust mengalami burnout yang cukup berat dan aktivitas mereka berkurang, dan hampir tidak ada orang yang melanjutkan pekerjaan itu. Meski begitu, cukup menyenangkan melihat orang-orang dari Google membuka sebuah PR untuk mengoptimalkan tata letak memori variabel yang ditangkap
      Saya dan rekan kerja banyak memakai async, jadi mungkin kami sendiri harus mengerjakannya, atau setidaknya memulainya. “Gratis” di sini tampaknya lebih mirip gratis seperti memelihara anak anjing
      Jadi ya, judulnya memang sedikit clickbait, tetapi saya tetap tidak berniat menariknya kembali
    • Saya setuju judulnya terlalu berlebihan
      Penulis tampak terlalu terobsesi dengan overhead fungsi kecil. Ia terganggu oleh overhead status “panic” dan “returned”, padahal itu bukan masalah besar
      Sebagian besar blok async yang berguna cukup besar sehingga overhead kasus error tertutupi
      Soal kurangnya inlining, mungkin memang ada benarnya. Tetapi yang biasanya membatasi jumlah aktivitas dalam skala besar adalah ruang state yang dibutuhkan tiap aktivitas
  • Secara umum async tampak seperti ide yang belum matang. Kode biasa pun sebenarnya sudah asinkron
    Jika Anda harus menunggu pekerjaan async, thread akan tidur sampai siap dan kernel mengabstraksikannya untuk Anda. Lalu karena orang tidak suka menyusun kode sebagai thread logis, mereka menambahkan sistem callback untuk event, dan setelah itu sadar bahwa callback sulit dipahami serta kontrol berurutan lebih baik
    Jadi saya melihat thread adalah model pemrograman yang benar
    Sekarang runtime bahasa lebih menyukai “green thread” demi portabilitas dan performa, tetapi kebanyakan bahasa tidak benar-benar menyediakannya dengan baik. Sebagai gantinya, muncul masalah warna async/non-async, scheduling, prioritas, dan non-preemption. Model scheduling dan proses yang lebih buruk daripada tahun 1970-an

    • Pernyataan bahwa “kode biasa juga sudah async, dan saat menunggu thread tidur lalu kernel mengabstraksikannya” tidak tepat
      Kode async pun sering ditulis dengan cara yang gagal memaksimalkan konkurensi yang dapat diekspresikan. Misalnya bukan “jalankan N pekerjaan I/O sekaligus”, tetapi ditulis seperti “untuk setiap pekerjaan X, await process(x)”
      Namun di dunia thread, masalah konkurensi ini justru lebih parah. Thread pada dasarnya terlalu berat untuk mengekspresikan konkurensi secara efisien, dan tidak ada cara untuk mengoptimalkannya ke arah itu
      Ini bukan pelajaran baru. Sudah lama diketahui bahwa executor work-stealing punya latensi jauh lebih rendah dan P99 yang lebih konsisten daripada thread tradisional. Itulah alasan Apple membuat GCD pada awal 2000-an
      Thread tidak memberi scheduler kernel informasi yang cukup kaya untuk memahami beban kerja, dan thread kernel adalah mekanisme yang terlalu berat untuk mendapatkan konkurensi yang halus. Untuk beban yang bukan murni komputasi, melainkan I/O atau campuran, hasilnya malah lebih buruk
      Tidak semua program butuh tingkat performa seperti ini, tetapi dengan usaha yang sama jauh lebih mudah mencapai standar performa yang lebih tinggi, dan pada praktiknya Anda bisa mendapatkan latensi dan throughput yang sulit dikejar pendekatan tradisional
      Tanda bahwa arah async itu benar juga terlihat dari io_uring. Pendekatan I/O performa tinggi di kernel melalui io_uring benar-benar berbeda dari threading dan system call tradisional, dan penanganan completion-nya jauh lebih dekat dengan konkurensi async. Hanya saja async/await saja tidak punya cukup “warna” untuk mengekspresikan hubungan antar tugas async, sehingga lebih sulit dimanfaatkan sepenuhnya
    • Begitu kernel dan scheduler OS ikut campur, kecepatannya bisa menjadi 3~4 orde magnitudo lebih lambat dari yang semestinya mungkin
      Terakhir kali saya mengutak-atik kode coroutine/scheduling, membuat thread yang langsung selesai lalu melakukan join memakan sekitar 200µs, sedangkan membuat green thread sendiri, menjadwalkannya, lalu menunggunya hanya sekitar 400ns
      Tidak perlu menunggu 10 tahun sampai seseorang lagi-lagi merancang framework async yang absurdly rumit. Di bahasa sistem mana pun, dengan 20 baris assembly Anda bisa langsung membuat green thread/coroutine bertumpuk sendiri
    • Apakah “thread adalah model pemrograman yang benar” bergantung pada apa yang Anda kerjakan. Untuk pekerjaan yang berpusat pada komputasi, thread cocok; untuk pekerjaan yang berpusat pada bandwidth, async lebih cocok
      Optimisasi kode yang berpusat pada bandwidth adalah masalah desain jadwal. Dalam model multithreading klasik, kontrol atas scheduling terbatas, tetapi dalam model async, kontrolnya hampir sempurna
      Jadwal async yang dioptimalkan dengan baik jauh lebih cepat daripada arsitektur multithread setara untuk pekerjaan berpusat pada bandwidth yang sama, sampai tidak layak dibandingkan
      Sebagian besar kode performa tinggi saat ini berpusat pada bandwidth, dan async ada untuk membuat beban kerja seperti ini lebih mudah dioptimalkan
    • Menurut saya callback justru lebih mudah dipahami
      Saat menguji pemrosesan konkuren dan memastikan race condition ditangani dengan benar, callback jauh lebih mudah karena Anda bisa mengendalikan scheduling. Karena tiap callback merepresentasikan unit terpisah, Anda bisa melihat event mana yang dapat diurutkan ulang, dan lebih mudah meninjau berbagai urutan
      Sebaliknya, pada thread orang mudah mengabaikan urutan, dan tidak memikirkan kapan kompleksitas dari thread lain bisa memengaruhi thread saat ini. Itu bukan sederhana, melainkan lebih seperti penyederhanaan
      Selain itu, tanpa menambahkan penghalang buatan untuk menghentikan thread, atau tanpa mengganti I/O dengan stub dan meneruskan mock yang memiliki callback untuk mengontrol urutan, sulit benar-benar mengubah dan menguji skenario konkurensi
      Masalah callback adalah call stack yang tertangkap bukan call stack logis. Kecuali ada library/runtime tertentu yang berusaha membuat call stack bermakna, Anda membutuhkan definisi error yang baik
      Tentu saja, Anda juga bisa mencampur dua paradigma ini dan hanya mendapatkan kelemahan keduanya
    • Thread bukan lebih baik atau lebih buruk dari async+callback, melainkan model yang berbeda. Ada masalah yang cocok untuk thread, dan ada juga masalah yang jauh lebih baik diekspresikan dengan async
  • Jika tujuan utama Rust adalah keamanan, saya tidak paham kenapa ada panic. Seharusnya kita bisa membuktikan bahwa tidak ada jalur yang mungkin panic dalam kode
    Saya memeriksanya sepanjang minggu ini, dan ternyata sangat sulit membuat program yang dijamin tidak pernah panic. Setahu saya panic handler ukurannya sekitar 300KB, dan satu-satunya cara untuk mengecualikannya adalah kode Anda memang sama sekali tidak punya jalur yang bisa panic saat kompilasi. Mengecek apakah panic handler ikut masuk ke biner setelah kompilasi terasa seperti hack
    Anda memang bisa melarang unwrap dan operasi panic lain lewat lint, tetapi andai ada subset Rust tanpa panic, banyak masalah yang dibahas tulisan ini akan hilang
    Sangat menjengkelkan berurusan dengan bahasa yang punya begitu banyak operasi yang secara teoretis bisa panic, padahal pada praktiknya itu hanya mungkin terjadi pada hal setingkat bit flip, sama seperti saat membuktikan array tidak kosong atau saat menangani async
    Akhirnya Anda harus menambahkan banyak sekali penanganan error untuk situasi yang tidak akan pernah benar-benar terjadi, atau memakai struktur aneh seperti pola list tak-kosong dengan field pertama dan sisa list terpisah. Dan struktur itu sendiri juga menambah bloat-nya sendiri

    • Di sisi Rust-in-Linux mereka sedang menangani masalah ini dengan hal-hal seperti operasi memori yang bisa gagal. Bagi mereka, ini memang fitur yang diperlukan
      Pekerjaan untuk memperluas penggunaan berbasis pembuktian, termasuk pembuktian bahwa array tidak kosong, juga sedang berjalan pelan-pelan
    • panic cukup penting untuk kegunaan dan keamanan
      Jika tidak ada panic dan semua situasi harus terus dieksekusi, maka untuk situasi seperti korupsi memori yang merusak invariant, Anda perlu menambahkan banyak penanganan error di semua tempat yang memeriksa invariant demi mencoba pemulihan
      Itu justru jenis masalah yang sama persis dengan yang Anda khawatirkan, yaitu penanganan error besar-besaran untuk situasi yang nyaris tidak pernah terjadi
    • Tujuan Rust adalah keamanan memori. panic sepenuhnya aman dari sudut pandang keamanan memori
    • Bahkan OS yang menjalankan program Anda pun tidak sempurna
      Saya cukup lelah dengan sikap yang menginginkan alat membuat semuanya mustahil gagal tanpa orang itu sendiri mau melakukan apa pun. Mereka ingin API yang mudah, lalu kalau itu belum cukup mudah mereka ingin container Kubernetes yang “diprogram” dengan YAML, dan kalau itu pun belum cukup mudah mereka ingin layanan hosting klik-klik dari GCP atau Amazon
      Pada akhirnya itu terasa lebih seperti ingin mengonsumsi aplikasi yang tidak pernah gagal, bukan ingin memrogram, dan gaya hidup seperti itu hanya bisa ada di atas hubungan simbiosis dengan orang-orang yang benar-benar membangun sesuatu
  • Diskusi seperti ini, jelek tetapi perlu, juga sudah berlangsung cukup lama di C++
    Sejak async diperkenalkan di Rust, saya memang tidak suka sifatnya yang menular
    Saya berharap Rust berhasil, dan kalau lebih banyak orang seperti ini muncul, masa depan Rust bisa jadi lebih cerah

  • Saya baru mulai mengerjakan async Rust belakangan ini, dan masalah utama yang saya rasakan sekarang adalah duplikasi kode
    Setiap fungsi yang ingin mendukung API async dan API blocking harus ditulis dua kali. Rasanya akan bagus kalau ada maybe-async
    Saya mencoba mengakalinya dengan melihat crate seperti maybe-async dan bisync, tetapi semuanya punya masalah atau batasan yang berat

    • Sedang ada pekerjaan pada keyword generics yang memungkinkan fungsi dibuat generik terhadap keyword seperti async atau const
      Untuk saat ini, pilihan terbaik untuk menulis kode yang ingin hidup di dunia sinkron maupun asinkron adalah sans-io. Thomas Eizinger dari Fireguard menulis artikel yang bagus tentang pola ini[1]
      Pola ini bukan hanya menyelesaikan masalah sync/async dengan rapi, tetapi juga mempermudah testing, dan membuka jalan ke teknik seperti DST[2]
      Saya juga menulis artikel tentang topik ini[3], dan menekankan bahwa masalahnya lebih luas daripada async versus sync, karena juga mencakup executor yang berbeda-beda
      0: https://github.com/rust-lang/effects-initiative
      1: https://www.firezone.dev/blog/sans-io
      2: https://notes.eatonphil.com/2024-08-20-deterministic-simulat...
      3: https://hugotunius.se/2024/03/08/on-async-rust.html
    • Sangat bergantung pada apa yang sebenarnya Anda lakukan, tetapi kalau cukup sederhana, mungkin Anda bisa membuat macro yang menukar tipe dan await
    • Ini adalah masalah warna fungsi klasik. https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
    • Dari sudut pandang saya, fungsi async sudah merupakan maybe-async
      Perbedaan antara fn -> void dan fn -> Future adalah yang pertama langsung berjalan sampai selesai, sedangkan yang kedua mungkin baru selesai nanti
      Jika Anda ingin menjalankan fungsi async secara blocking, cukup pakai executor blocking
  • Saya suka tulisan ini karena membuat saya juga melihat tujuan Rust 2026
    Tim kami memakai Rust, tetapi kami belum perlu masuk terlalu dalam untuk melakukan pekerjaan yang kami butuhkan. Meski begitu, menyenangkan melihat bahasa dengan banyak umpan balik komunitas berkembang dari bawah
    Di C++ saya tidak terlalu merasakan alur seperti ini, dan di area lain saya juga tidak terlalu tahu bagaimana prosesnya berjalan
    Tapi satu hal yang agak disayangkan adalah tiap tujuan tampaknya membutuhkan pendanaan khusus, jadi agak terasa seperti Kickstarter. Saya penasaran apakah ini model terbaik yang sudah ditemukan sejauh ini

    • Istilah “tujuan proyek” sebenarnya cukup menyesatkan dibanding makna nyatanya
      Tujuan proyek adalah sistem di mana satu orang atau kelompok kecil menyatakan ingin mengerjakan sesuatu, lalu meminta relawan proyek Rust menyediakan waktu dukungan berkelanjutan seperti code review atau menjawab pertanyaan
      Itu tidak berarti proyek Rust sendiri yang menetapkan tujuan tersebut, atau pasti mendukungnya
      Jadi kurang tepat melihatnya sebagai roadmap resmi Rust; lebih tepat dipahami sebagai “ada kontributor yang ingin bekerja di area ini”
    • Tampaknya bahkan di dalam komite ISO C++ sendiri ada semacam konsensus bahwa proses evolusi bahasa itu agak rusak. Sebagian besar karena skala dan cara organisasinya
      Kalau teknologi sudah mapan secara komersial, sayangnya hal seperti ini memang sering terjadi. Sulit juga menyalahkan sponsor besar karena hanya mendanai area yang mereka pedulikan
      Untungnya, setahu saya sebagian besar pendanaan TweedeGolf berasal dari pemerintah Belanda
    • Dalam pekerjaan open source tampaknya ada kira-kira dua jenis: pengembangan fitur dan pemeliharaan
      Fitur baru bisa “dijual”. Membangunnya memang butuh uang, tetapi ia menyelesaikan masalah nyata, dan jika biaya masalah itu lebih besar daripada biaya pengembangan fitur, perusahaan biasanya bersedia membayar
      Pemeliharaan lebih sulit, tetapi sekarang sudah ada juga dana untuk maintainer. Contohnya adalah dana RustNL: https://rustnl.org/maintainers/
      Dana seperti ini menargetkan pekerjaan yang lebih luas dan berkelanjutan, dengan banyak organisasi menyumbang sedikit demi sedikit untuk menopangnya
      Saya tidak tahu apakah itu model terbaik, tetapi setidaknya tampaknya cukup bekerja
  • Jika membaca dokumentasi Rust Async dan Tokio, sebenarnya sudah dijelaskan dengan baik kenapa bagian yang intensif CPU tidak boleh dimasukkan ke stack async, cara memakai alat dasar seperti std::sync::Mutex secara efisien di dalam blok async, dan cara menyambungkan kode sinkron dengan kode async
    Banyak kode tidak mengikuti panduan ini karena tidak peduli atau tidak membutuhkan efisiensi. Tetapi ada banyak proyek yang memang mementingkan performa dan efisiensi, dan begitu kodenya berjalan di produksi, jebakannya mulai terlihat. ScyllaDB adalah salah satu contohnya
    LLM juga tidak membantu. Ia menjadikan semuanya async sampai main, memakai alat dasar yang salah, dan tidak merancang sistem dengan benar

  • Melipat state yang duplikat, yaitu pola menarik match ke luar cabang await seperti pada contoh process_command, adalah cara termudah yang bisa diterapkan siapa pun pada kode async yang ada hari ini
    Tidak butuh pekerjaan compiler, cukup refactoring

    • Setidaknya perlu ada lint kustom yang bisa mencari tempat-tempat yang memungkinkan pola itu diterapkan. Pada tingkat tertentu, itu sudah cukup dekat dengan pekerjaan compiler
  • Soal bagian “Future tidak mudah di-inline”, di bahasa pemrograman yang saya buat sendiri saya menulis custom pass untuk meng-inline pemanggilan fungsi async di dalam fungsi async
    Secara umum hasilnya baik dan bisa menghilangkan sebagian boilerplate, tetapi ukuran biner hasilnya membengkak cukup banyak
    Secara teknis Rust juga bisa melakukan hal yang sama

 
GN⁺ 2026-05-06
Komentar Lobste.rs
  • Ternyata ini tulisan yang jauh lebih konstruktif daripada yang saya perkirakan hanya dari judulnya

    • Menurut saya ini cukup dekat dengan fakta. Sudah 7 tahun sejak MVP dirilis, tetapi hampir tidak ada kemajuan baik dalam desain bahasa maupun implementasi compiler, dan setelah orang-orang yang terutama membuat MVP itu mengurangi keterlibatan mereka dalam proyek pada periode yang kurang lebih sama, proses estafet sesudahnya seakan berhenti
      Saya berharap siapa pun yang ingin mengerjakan ini mendapat dukungan yang dibutuhkan
  • I want to work on this in the compiler and as such have submitted it as a Project Goal

    Stop generating statemachines that don’t have to be there
    Make the compiler’s job easier by removing panic paths and branches
    Make statemachines smaller

    Senang melihat masalah ini sedang ditangani. Saya sudah beberapa kali melihat tulisan bahwa saat ini rustc mengirim terlalu banyak kode ke LLVM dan berharap optimizer menangani semuanya, dan khususnya tulisan ini juga meminta pendanaan untuk pekerjaan tersebut

  • Astaga, saya yang bodoh
    Saya selalu mengira async pada dasarnya pasti “gemuk” karena bagaimanapun juga ia memerlukan runtime, pelacakan task, dan polling untuk memeriksa penyelesaian. Overhead itu kan memang tidak nol
    Saya menganggap “abstraksi tanpa biaya” yang dibicarakan di sini merujuk pada fitur bahasa, terpisah dari runtime tambahan yang menyertainya
    Saya bahkan tidak pernah terpikir untuk melihat apa yang sebenarnya dikeluarkan rustc sebelum diserahkan ke LLVM

  • Untuk orang yang belum akrab dengan async Rust:

    It's amazing how we can write executor agnostic code that can run concurrently on huge servers and tiny microcontrollers.

    Ini memang benar sekali. Bahkan pohon panggilan async yang bersarang, setelah dioptimalkan semaksimal mungkin, akan mengeras menjadi satu struct tunggal dengan state machine di dalamnya. Benar-benar pendekatan yang cerdas

  • Apakah jika mencapai kasus ini dalam build rilis akan muncul semacam deadlock? Atau mungkinkah terjadi kebocoran karena task-task menunggu pekerjaan yang selalu Pending?

    • Betul. Future seperti itu akan terhenti dan tidak akan pernah selesai. Namun keadaan seperti itu hanya bisa dicapai dari kode async tingkat rendah yang memang sudah buggy, dan kode yang gagal melacak future yang sudah selesai dengan benar kemungkinan besar memang sudah menyebabkan kebocoran dan deadlock
      Dengan .await, polling yang keliru tidak mungkin terjadi
  • Ada beberapa hal yang terpikir:

    1. Tulisan ini tampak seperti argumen bahwa lebih banyak logika optimisasi seharusnya dipindahkan keluar dari LLVM ke lapisan MIR. Misalnya, saya paham kenapa inline async function lebih mudah dilakukan di MIR daripada di LLVM. Jika itu bisa dilakukan untuk async di MIR, saya jadi bertanya-tanya apakah logika itu bisa digeneralisasi ke function sinkron juga, lalu sebagian pass optimisasi LLVM dihapus. Saya tahu ini pekerjaan besar, dan ini lebih ke arah orientasi daripada pertanyaan praktis. Begitu compiler frontend/middle-end menjadi cukup kompleks, sepertinya mungkin lebih baik kalau cukup banyak optimisasi umum LLVM dipindahkan ke tempat lain
    2. Saya tetap tidak suka panic=unwind. Selain beberapa test harness, saya hampir tidak pernah melihat keunggulan dibanding panic=abort yang cukup untuk menutup biayanya. Bahkan untuk test harness pun, di Linux sepertinya mungkin menerapkan pilihan serupa dengan menggunakan clone secara agak aneh untuk wait thread eksekusi alih-alih pthread_join. Bisa jadi saya salah soal ini
  • Apakah tautannya baru saja mati juga buat orang lain?
    Sunting: tulisan blog muncul sekitar setengah detik lalu pindah ke halaman 404
    Sunting 2: saya masuk ke daftar tulisan blog, mengklik beberapa hal, dan bahkan saat membuka tulisan itu dari daftar tetap menuju halaman 404. Bagaimana bisa blog yang statis, atau setidaknya seharusnya statis, dibuat rusak seperti ini?

    • Nadanya terasa agak terlalu tidak perlu kasar dan menyerang. Situs web juga bisa punya bug, dan melaporkannya memang berguna, tetapi komentar ini terdengar agak menyebalkan
      Sebagai catatan, saya mengikuti langkah reproduksi yang tampaknya sama dan sama sekali tidak mendapatkan 404. Saya mencobanya di ponsel dan desktop, dengan JavaScript aktif maupun nonaktif. Jadi sepertinya gejala yang dialami mungkin lebih rumit daripada yang terlihat