10 poin oleh GN⁺ 2025-08-28 | 3 komentar | Bagikan ke WhatsApp
  • Rust meningkatkan produktivitas dan kemudahan pemeliharaan karena refactoring dapat dilakukan dengan percaya diri bahkan pada codebase besar berkat jaminan keamanannya yang kuat
  • Compiler dapat mendeteksi bug terkait penjadwalan asinkron lebih awal dan memperkuat stabilitas dengan mencegah undefined behavior
  • Bahasa seperti TypeScript sering kali baru menemukan bug asinkron di lingkungan produksi karena sistem tipenya lebih longgar
  • Sistem tipe Rust secara jelas menunjukkan dampak dari perubahan kode, sehingga meningkatkan kepercayaan dan kemauan untuk bereksperimen dalam proyek yang kompleks
  • Berbeda dengan Rust, Zig memiliki pemeriksaan yang lebih longgar dalam penanganan error sehingga bug akibat typo bisa terlewat dan keandalannya lebih rendah

Ringkasan dan Latar Belakang

  • Backend Lubeno ditulis 100% dengan Rust, dan codebase-nya telah tumbuh hingga mencapai tahap di mana sulit memahami keseluruhannya hanya di dalam kepala
    • Proyek berskala besar umumnya mengalami penurunan produktivitas karena sulit memeriksa efek samping dari perubahan
  • Jaminan keamanan Rust secara jelas memberi tahu dampak saat kode diubah, sehingga mengurangi rasa takut terhadap refactoring
    • Hal ini berkontribusi pada peningkatan kemudahan pemeliharaan dan produktivitas jangka panjang
  • Tulisan ini dimulai dari sebuah kasus ketika compiler Rust mendeteksi bug asinkron, lalu mengeksplorasi keunggulan produktivitas Rust

Contoh Jaminan Keamanan Rust

  • Situasi masalah: sebuah struct dibungkus dengan mutex untuk akses bersamaan, lalu operasi asinkron dijalankan setelah lock diperoleh
    let lock = mutex.lock();  
    db.insert_commit(commit).await;  
    
  • Penemuan masalah: rust-analyzer tidak menampilkan error, tetapi terjadi error kompilasi di file definisi router
    .route("/api/git/post-receive", post(git::post_receive))  
                                         ^^^^^^^^^^^^^^^^^  
    error: future cannot be sent between threads safely  
    
  • Analisis penyebab:
    • Web framework membuat task asinkron untuk setiap koneksi HTTP, lalu scheduler memindahkan task antar-thread
    • Mutex harus dilepas pada thread yang sama, dan jika thread berpindah di titik .await, bisa terjadi undefined behavior
    • Compiler Rust melacak masa hidup lock dan mendeteksi kemungkinan lock dilepas di thread lain
  • Cara menyelesaikan: lepaskan lock sebelum .await
  • Makna pentingnya: Rust mencegah bug asinkron yang sulit direproduksi di lingkungan pengembangan pada saat compile time

Contoh Perbandingan dengan TypeScript

  • Situasi masalah: terjadi bug redirect asinkron pada kode TypeScript
    if (redirect) {  
        window.location.href = redirect;  
    }  
    let content = await response.json();  
    if (content.onboardingDone) {  
        window.location.href = "/dashboard";  
    } else {  
        window.location.href = "/onboarding";  
    }  
    
  • Penyebab masalah:
    • window.location.href tidak langsung melakukan redirect, melainkan menjadwalkannya, sehingga eksekusi kode tetap berlanjut
    • Akibat race condition, terjadi redirect yang tidak diinginkan
  • Cara menyelesaikan: tambahkan return pada blok if
    if (redirect) {  
        window.location.href = redirect;  
        return;  
    }  
    
  • Keterbatasan: TypeScript tidak memiliki pelacakan lifetime atau aturan peminjaman, sehingga bug seperti ini tidak bisa dideteksi pada saat compile time
    • Bug baru ditemukan di lingkungan produksi dan membutuhkan waktu lama untuk debugging

Keunggulan Refactoring di Rust

  • Dalam pengembangan web, Python, Ruby, dan JavaScript/Node.js memiliki produktivitas awal yang tinggi, tetapi ketika codebase membesar, perubahan menjadi sulit karena keterikatan yang longgar
    • Setelah perubahan, error tak terduga bisa muncul dan menurunkan kemauan untuk memperbaiki kode
  • Rust membuat sistem tipe secara jelas menunjukkan dampak perubahan, sehingga mengurangi rasa takut terhadap refactoring
    • Contoh: peringatan seperti “perubahan ini bisa memengaruhi bagian lain” membantu mencegah masalah lebih awal
  • Bahkan ketika codebase terus tumbuh, produktivitas meningkat, kode lama dapat digunakan kembali, dan stabilitas tetap terjaga saat melakukan perubahan

Perbandingan dengan Pengujian

  • Pengujian berguna untuk mencegah regresi saat refactoring, tetapi karena tidak dipaksakan oleh compiler, pengujian bisa saja dilewati
    • Menulis pengujian menimbulkan beban mental karena harus menentukan tingkat abstraksi, perilaku vs detail implementasi, dan apakah pengujian benar-benar mencegah kesalahan
  • Rust membuat compiler memblokir kesalahan umum sejak awal, sehingga mengurangi beban pengambilan keputusan yang biasanya ditanggung pengujian
    • Sifat yang tidak dapat diverifikasi oleh sistem tipe dapat dilengkapi dengan pengujian

Perbandingan dengan Zig

  • Zig adalah bahasa system programming yang mirip dengan Rust, tetapi lebih longgar dalam penanganan error
    • Contoh: kode penanganan error
      const FileError = error{ AccessDenied };  
      fn doSomethingThatFails() FileError!void {  
          return FileError.AccessDenied;  
      }  
      pub fn main() !void {  
          doSomethingThatFails() catch |err| {  
              if (err == error.AccessDenid) {  
                  std.debug.print("Access was denied!\n", .{});  
              }  
          };  
      }  
      
    • Karena typo AccessDenid, bug pun terjadi, tetapi compiler Zig memperlakukannya sebagai angka sehingga kompilasi tetap berhasil
  • Saat menggunakan pernyataan switch, typo dapat dideteksi, tetapi pada pernyataan if diabaikan, sehingga menimbulkan masalah keandalan
  • Rust mencegah celah desain seperti ini dan memeriksa typo maupun kesalahan logika dengan ketat

Implikasi

  • Rust meningkatkan produktivitas dan stabilitas proyek besar melalui jaminan keamanan dan sistem tipe yang ketat
  • Masalah kompleks seperti bug asinkron juga dapat dideteksi pada saat compile time, sehingga biaya pemeliharaan berkurang
  • Contoh TypeScript dan Zig menunjukkan risiko dari pemeriksaan yang longgar, sekaligus menegaskan nilai compiler Rust yang ketat
  • Rust bukan hanya unggul dalam produktivitas awal untuk pengembangan web, tetapi juga menjadi alat yang kuat untuk pengelolaan codebase jangka panjang

3 komentar

 
taptaps 2025-08-30

Setiap kali melihat orang bilang ini yang terbaik, ini bahasa yang kuat banget!!
Saya malah jadi berpikir, jangan-jangan karena jumlah developer Rust ternyata tidak sebanyak yang dibayangkan, jadi mereka mengiming-imingi orang supaya pakai Rust??

 
colus001 2025-08-29

Artikel rekomendasi terkait Rust terasa seperti sikap seorang gourmet yang berkata, "Coba! Coba!" — mungkin cuma saya yang merasa begitu?

 
GN⁺ 2025-08-28
Komentar Hacker News
  • Tahun lalu saya mem-port driver jaringan virtio-host yang ditulis dengan Rust. Saya mengganti backend, mekanisme interrupt, dan mengubahnya dari library menjadi proses mandiri. Programnya cukup kompleks, menangani memory mapping, interrupt VM, socket jaringan, sampai multithreading. Pengalaman saya dengan Rust hampir tidak ada, dan pengalaman dengan virtio juga minim, tetapi saat proyeknya berhasil dikompilasi, hasilnya langsung berjalan dengan sempurna. Selain satu bug terkait Drop yang mudah diperbaiki. Saya rasa ini sangat terbantu karena library-library Rust dirancang dengan struktur yang sulit disalahgunakan

    • Saya sudah lama mengembangkan dengan Rust, dan kebanyakan kalau sudah berhasil compile, kodenya memang langsung berjalan dengan baik. Kadang masih ada bug deadlock atau urutan eksekusi, tetapi pada dasarnya compile yang sukses berarti sebagian besar proyek sudah bekerja dengan benar
  • Saya pikir Rust itu hebat. Tapi saya tidak setuju dengan pendapat bahwa bug assignment href itu salah TypeScript. Inti masalahnya adalah meskipun href disetel, perpindahan halaman tidak terjadi seketika melainkan diproses belakangan. Masalah yang sama bisa saja terjadi di Rust. Kalau di Rust ada fungsi set_href dan perilaku ini diproses belakangan, maka kode seperti ini tetap mungkin terjadi:

    set_href('/foo')

    if (some_condition) { set_href('/bar') }

    Menurut saya, di Rust API seperti ini tidak akan didesain seperti itu. Tindakan yang terjadi dari setter bukanlah desain library yang baik, dan aneh juga kalau assignment href tidak langsung memindahkan halaman. Kalau ini standard library Rust, implementasi konyol seperti ini tidak akan ada. Jadi ini bukan soal Rust vs TypeScript, melainkan perbedaan antara standard library Rust dan Web Platform API. Saya setuju bahwa Rust tidak akan memberikan pengalaman pengguna seperti ini

    • Secara formal, merancang setter agar langsung memicu aksi memang tidak ideal. Penamaannya pun seharusnya diubah menjadi sesuatu seperti navigate_to(href). Di lingkungan browser, semua kode JS berjalan lewat callback dan dikendalikan oleh event loop, jadi wajar juga kalau tidak dieksekusi secara instan

    • Contoh Rust itu menarik, tetapi dari contoh TypeScript saja kita tidak bisa menyimpulkan apakah TS cocok untuk proyek besar. Di Ruby saya sering harus menangkap bug saat runtime sehingga terasa tidak nyaman, tetapi pada akhirnya saya tetap puas karena sebelum commit semuanya berjalan baik, dan kodenya mudah dibaca serta dimodifikasi. Isu perpindahan lokasi itu masalah JavaScript dan bagian yang diwarisi TS. Ini terjadi karena JS memungkinkan properti dimodifikasi sesuka hati. Namun halaman juga tidak langsung hilang, jadi setelah tahu perilakunya, sebenarnya itu cukup masuk akal

    • Secara teknis, di Rust maknanya bisa diberi petunjuk lebih jelas lewat apakah set_href mengembalikan () atau !. Tapi dalam kasus redirect bersyarat, tetap sulit mencegah penyalahgunaan sepenuhnya

    • Maksud saya adalah bahwa dengan model ownership Rust, API bisa didesain agar saat window.set_href('/foo') dipanggil, ownership atas window ikut diambil sehingga pemanggilan kedua menjadi tidak mungkin. TypeScript sama sekali tidak punya konsep pelacakan lifetime, jadi hal seperti ini tidak mungkin. Karena API JS-nya sendiri sudah ada, tidak ada cara bagi TypeScript untuk memperkenalkan sistem ownership. Saya ingin menunjukkan ini sebagai contoh bagaimana berbagai fitur Rust berpadu untuk memberi jaminan yang lebih kuat

    • Dasar argumenmu bahwa Rust lebih baik akhirnya terdengar seperti “programmer Rust lebih hebat”. Rasanya programmer Rust sendiri tidak akan memakai argumen melingkar seperti itu

  • Kode setelah assignment tetap akan terus dieksekusi kecuali ada early return yang eksplisit. Serius, saya tidak paham kenapa ada yang mengira assignment nilai akan menghentikan eksekusi skrip. Mungkin konteks pada contoh TS kurang, tetapi menjadikannya contoh “data race” juga terasa aneh

    • Memberi nilai ke window.location.href memang punya efek samping berupa browser berpindah ke tautan itu. Perilaku ini cukup mengejutkan, dan karena assignment sederhana bisa memuat halaman baru, rasanya mirip execve, jadi tidak aneh kalau orang mengira eksekusi JS akan langsung berhenti. Tentu kita tidak seharusnya mengandalkan asumsi seperti itu saat memprogram, tetapi karena perilakunya memang agak aneh, saya paham kenapa orang bisa bingung

    • Terlepas dari apakah orang memikirkannya atau tidak, bug seperti ini begitu diberi tahu seseorang akan jelas cara memperbaikinya. Inti argumen penulis adalah bahwa bug seperti ini, yang tidak bisa ditangkap TS, dalam praktiknya bisa sulit dicari dan memakan waktu lama

    • exit(), execve(), dan sejenisnya memang benar-benar menghentikan eksekusi saat itu juga, jadi wajar kalau orang menganggap redirect akan berperilaku sama

    • Aneh kalau orang dipermasalahkan hanya karena membagikan pengalamannya sendiri

    • Assignment ini punya efek samping besar, yaitu membuat halaman ditinggalkan. Jadi menganggapnya sebagai aksi asinkron yang terjadi seketika juga bukan asumsi yang terlalu aneh. Saya sendiri pernah membuat asumsi seperti itu

  • Ini hanya cerita bahwa seorang developer menyadari static type system itu berguna. Setiap kali melihat tulisan seperti ini selalu terasa lucu

    • Yang ingin saya tunjukkan di blog saya adalah bahwa pelacakan lifetime dan sistem trait Rust bisa menangkap isu yang jauh lebih kompleks daripada sekadar ketidakcocokan tipe. TypeScript juga bahasa bertipe statis, tetapi tidak bisa memberi jaminan sekuat Rust
  • Bukankah sebagian besar kelebihannya pada akhirnya datang dari penggunaan static typing, yaitu bahasa yang dikompilasi? Java, Go, dan C++ juga begitu. TypeScript punya trik tersendiri: ia dikompilasi ke JS dan mewarisi masalah JS, tetapi tetap cukup berguna. Rust punya sistem tipe yang lebih ketat sehingga bisa melakukan pengecekan tambahan saat compile time, tetapi sebagai gantinya lebih sulit dipelajari dan juga lebih sulit dibaca menurut saya

    • Saya agak setuju, tetapi Rust punya lebih banyak dimensi dalam sistem tipenya: ownership, akses bersama/eksklusif, thread safety, sum type, dan lain-lain. Berkat sistem ownership/borrowing, jadi jelas apakah argumen yang diteruskan hanyalah view sementara atau benar-benar dipindahkan sepenuhnya. Ini sangat menguntungkan untuk program besar atau saat memakai library eksternal. Misalnya, tipe slice di Go tidak selalu jelas pada runtime operasi apa yang diizinkan, dan juga kurang jelas bagaimana cara meminjamnya sebagai read-only. Rust bisa menjamin thread safety di level sistem tipe, sehingga data race yang sulit ditemukan saat runtime di bahasa lain bisa dicegah sejak compile time

    • Kalau semua bahasa bertipe statis dianggap satu kelompok yang sama, itu karena Anda mungkin belum benar-benar merasakan kekuatan union(sum) type dan pattern matching. Sekali sudah terbiasa dengan union type, bahasa statis tradisional lain jadi terasa kurang memuaskan

    • Salah satu keunggulan besar adalah traits/impl traits. Di Rust, trait bisa ditambahkan ke tipe mana pun belakangan, mirip seperti Extension Method di C#. Di kebanyakan bahasa, tipe biasanya sudah tetap ketika didefinisikan di library, tetapi di Rust kita bisa terus menumpuk kemampuan di atas tipe sederhana secara bertahap. Sifat late-bound ini memberi unsur dinamis pada sistem tipenya. Kalau mau agak ekstrem, superpower sejati Rust bukan borrow checker, melainkan keterbukaan dan fleksibilitas sistem tipenya. Tidak perlu merancang semuanya dari awal; cukup diperluas sedikit demi sedikit

    • Tidak semua bahasa bertipe statis memberi hasil yang sama. Java pada akhirnya bergantung pada Object dan casting runtime. Go tidak punya enum. C++ memang menambahkan konsep variant, tetapi untuk memakainya dengan aman tetap butuh penanganan manual semacam try/except, sehingga secara struktural terasa kurang nyaman

    • Orang bilang Rust sulit dipelajari, padahal kalau sudah benar-benar dipahami, sebenarnya tidak sulit. Di awal belajar ngoding, sering kali yang penting adalah bisa menulis sesuatu secara asal sampai akhirnya jalan, dan Rust memang bahasa yang kurang ramah terhadap gaya itu. Saya tidak akan merekomendasikannya sebagai bahasa pemula, tetapi untuk dibaca sebenarnya tidak sulit

  • Berkat safety Rust yang kuat, rasa percaya diri saat menyentuh codebase jadi jauh lebih besar. Dengan kepercayaan diri ini, refactor di bagian inti pun tidak terasa menakutkan, dan pada akhirnya produktivitas serta maintainability meningkat besar. Tapi justru untuk efek seperti inilah testing dipakai. Kalau tidak ada testing, compiler yang ketat memang sangat membantu, tetapi kalau test ditulis dengan baik, bahasa apa pun memungkinkan refactor dengan percaya diri

    • Lebih baik kalau hal yang memungkinkan dibuktikan secara statis oleh compiler. Testing paling optimal dipakai hanya untuk situasi yang sulit dijamin secara statis. Bentuk ideal paling puncak tentu formal verification, tetapi itu sangat sulit secara praktis, jadi ini bukan kaidah umum, meskipun secara prinsip benar

    • Testing yang baik dan sistem tipe yang dimanfaatkan dengan baik sama-sama efektif untuk menangkap bug. Tetapi menulis test kadang mengingatkan saya pada komik xkcd “Standards”. Mirip seperti memperbaiki standar dengan membuat standar baru, kita juga memperbaiki bug dengan menulis lebih banyak kode. Meski begitu, pemeliharaan sistem tipe ditangani perancang bahasa, jadi tidak perlu dikelola per proyek

    • Setiap kali refactor kode, test juga harus ikut direfactor, jadi pekerjaannya jadi dua kali lipat

  • Menurut saya, sistem tipe Rust atau F# paling bersinar saat melakukan refactor kode. Istilah refactoring tanpa rasa takut memang sangat pas

    • Kekurangannya, Rust tidak menoleransi kode yang belum selesai, jadi saat refactor tidak mungkin punya “kode yang sebagian sudah bekerja”. Harus diselesaikan sepenuhnya atau tidak sama sekali, sehingga kurang nyaman untuk menulis kode eksperimental. Tapi justru keketatan ini pada akhirnya jadi salah satu faktor yang menghasilkan kode yang baik
  • Contoh Zig itu mengejutkan. Kelihatannya terlalu tidak stabil sampai saya tidak paham bagaimana desain seperti itu bisa dianggap bagus

    • Saya rasa ini kemungkinan bug. Tetapi untuk bahasa yang sangat berpusat pada kreatornya seperti Zig, agar bug bisa diperbaiki, penting juga bahwa sang kreator mengakui itu sebagai bug. Kalau dianggap memang disengaja, desain seperti itu bisa saja terus dipertahankan

    • Semua bahasa punya sedikit desain yang rawan. Misalnya, di Go atau Zig kita selalu harus memanggil mutex.unlock() secara eksplisit, dan lock tidak otomatis dilepas saat keluar dari scope. Sebaliknya, konversi antartipe angka dengan operator as di Rust juga terlalu mudah, dan gara-gara itu saya pernah menghabiskan seharian mencari bug

    • Awalnya saya tidak melihat error itu, lalu sadar setelah membaca komentar ini

    • Saya membayangkan linter bisa saja memberi peringatan dengan menangkap referensi error yang tidak ada dalam sistem, dan menyarankan penggunaan switch

    • Saya tadinya mengira error set dibuat berdasarkan function signature. Cukup unik juga

  • Saya suka bahwa static type system yang kuat dan sound bisa memberi begitu banyak kemampuan. Saya juga pernah mengalami betapa mudahnya melakukan refactor besar-besaran di codebase Haskell (1 juta SLOC). Bahkan tanpa fitur yang sangat canggih pun, hanya dengan sistem tipe saja itu sudah memungkinkan

  • Rust memang mendeteksi dengan benar ketika lock masih dipegang di batas await, tetapi apakah melepas lock sebelum await itu benar-benar aman tetap membutuhkan konteks tambahan. Menurut saya lock harus tetap dipegang sampai transaction commit dibuat; kalau dilepas sebelum await, bisa timbul masalah konkurensi. Saya tidak terlalu paham Rust async, tetapi setelah commit bukankah seharusnya diblokir dengan join atau select

    • Kalau memang perlu mempertahankan lock saat await, gunakan async-aware mutex. Crate futures atau tokio mengimplementasikan lock seperti itu. Biasanya dipakai ketika lock dipegang lama atau perlu dipertahankan melewati await. Biayanya lebih tinggi dibanding lock biasa

    • Jika lock memang harus dipertahankan bahkan melewati batas await, Anda bisa memakai async-aware mutex dari Tokio. Lihat dokumentasi tokio/sync/struct.Mutex