Kamu nulis event loop untuk embedded device. Ada struct SensorReader yang pegang buffer internal dan futures yang reference buffer itu sendiri. Logikanya aman. Semua borrow selesai sebelum drop. Tapi cargo check teriak: “cannot borrow self as mutable because it is also borrowed as immutable”. Padahal konteks runtime-nya jelas nggak bakal konflik.

Kamu bukan yang pertama frustrasi. Pola self-referential di async block adalah pain point bertahun-tahun buat developer embedded dan systems programmer yang nulis event loop, interrupt handler, dan async state machine. Rust 1.85 akhirnya bawa perbaikan NLL (Non-Lexical Lifetimes) yang signifikan, dan borrow checker sekarang cukup pintar buat menerima pola yang sebelumnya ditolak mentah-mentah.

⚡ Jawaban Singkat / Key Takeaways: Rust 1.85 memperkenalkan analisis liveness berbasis borrow yang lebih granular di dalam async block, memungkinkan compiler mengenali bahwa reference ke self tidak hidup berdampingan dengan mutable borrow di titik yang sama secara runtime. Ini artinya self-referential structs di async context kini bisa compile tanpa unsafe atau workaround seperti Pin::as_mut projection yang kompleks.

Kenapa Self-Referential Struct di Async Block Susah Banget

Self-referential struct adalah struktur data yang salah satu field-nya memegang reference ke field lain dalam struct yang sama. Contoh sederhana di context event loop sistem tertanam:

// Pola umum di embedded event loop
struct SpiDriver {
    buffer: [u8; 256],
    dma: Option<DmaTransfer<'?, &'? [u8]>>,
    //                                    ^^^ reference ke buffer di atas!
}

Di Rust, ini problematik karena borrow checker melacak lifetime secara statis. Kalau dma pinjam buffer, maka SpuDriver nggak bisa dipindah (move) karena reference internal-nya invalid. Solusi klasik: Pin<Self>.

Tapi masalahnya: di async block, compiler generate state machine otomatis. State machine ini penuh dengan implisit move antar yield point. Jadi meskipun kamu udah bungkus struct dengan Pin, compiler lama tetap nggak bisa membuktikan bahwa borrow ke self nggak overlapping.

Kode Rust dengan borrow checker async block di editor VS Code untuk systems programming
Borrow checker generasi lama sering menolak pola async yang secara runtime aman

Apa yang Berubah di NLL Rust 1.85 untuk Async Block

NLL sendiri udah hadir sejak Rust 2018. Tapi NLL versi awal cuma bekerja di scope fungsi biasa. Di dalam async block, analisisnya masih konservatif karena generator/state machine transform terjadi sebelum borrow checking.

Rust 1.85 menambahkan dua perbaikan kunci:

  • Borrow liveness tracking lintas yield point: Compiler sekarang bisa membuktikan bahwa sebuah borrow ke field self.buffer tidak “hidup” di yield point yang sama di mana self di-mutable-borrow. Jadi kalau pola-mu aman secara temporal, compiler akan menerima.
  • Two-phase borrow di async context: Sebelumnya, two-phase borrow (mutable reference yang awalnya nggak aktif lalu jadi aktif) cuma berlaku di function body. Sekarang berlaku di async block juga, yang artinya pola “reserve mutable slot dulu, isi nanti” bisa jalan tanpa trik Option::take.

Simpelnya: borrow checker sekarang baca async state machine sebagai flow temporal, bukan cuma struktur statis.

Diagram NLL borrow checker Rust 1.85 yang menerima self-referential structs di async block
NLL improvements di Rust 1.85 menganalisis liveness borrow melintasi yield point

Contoh Nyata: Pola yang Dulu Error, Sekarang Compile

Event Loop Driver SPI (Sebelum vs Sesudah)

// Sebelum Rust 1.85: DITOLAK
impl SpiDriver {
    async fn transfer(&mut self, data: &[u8]) {
        self.buffer[..data.len()].copy_from_slice(data);
        let transfer = self.dma.start(&self.buffer[..data.len()]);
        // ERROR: cannot borrow `self.buffer` as immutable
        //        because `self` is also borrowed as mutable
        transfer.await;
    }
}

// Rust 1.85: DITERIMA
// Compiler paham bahwa `&self.buffer` bersih sebelum
// mutable borrow berikutnya aktif di yield point

Interrupt Handler dengan Deferred Work

struct UartRx {
    ring: RingBuffer<u8>,
    pending: Option<ReadFuture<'?, &'? RingBuffer<u8>>>,
}

impl UartRx {
    async fn read_byte(&mut self) -> u8 {
        loop {
            if let Some(byte) = self.ring.pop() {
                return byte;
            }
            // Dulu error: self.ring dipinjam immutable
            // sementara self di-mutable-borrow
            self.pending = Some(self.wait_for_interrupt());
            self.pending.as_mut().unwrap().await;
        }
    }
}

Pola ini umum banget di RTIC, Embassy, dan framework async embedded lain. Sebelum 1.85, kamu terpaksa pakai unsafe atau refactor ke channel-based architecture yang lebih boros memory.

Event loop architecture Rust dengan async block dan Pin projection yang kini diterima borrow checker
Arsitektur event loop + interrupt handler kini bisa ekspresif tanpa unsafe

Nggak Semua Pola Tiba-Tiba Jadi Aman

Jangan ekspektasi setiap self-referential async block langsung compile. Ada batasannya:

  • Borrow harus tidak tumpang tindih secara temporal. Kalau reference dan mutable borrow benar-benar hidup bersamaan di yield point yang sama, compiler tetap menolak.
  • Move antar yield point masih diawasi ketat. Kalau kamu .await di tengah borrow ke &self, compiler akan cek apakah self bisa dipindah saat itu.
  • Pin projection tetap diperlukan untuk self-referential murni. NLL improvement ini mengurangi false positive, bukan menghilangkan kebutuhan Pin.

Intinya: borrow checker sekarang lebih akurat membaca runtime flow async block kamu. Tapi dia tetap nggak kompromi soal soundness.

Dampak Langsung ke Ekosistem Embedded Rust

Framework embedded async seperti Embassy dan RTIC selama ini harus menyediakan abstraction layer yang menutupi keterbatasan borrow checker. Akibatnya: macro tebal, unsafe block terselubung, dan binary size yang nggak optimal.

Dengan NLL 1.85, library author bisa:

  • Menghapus workaround unsafe di modul interrupt handler
  • Mengurangi boxing dan alokasi heap yang sebelumnya diperlukan buat “mengelabui” borrow checker
  • Menyederhanakan API yang sebelumnya memaksa user ke pola callback-based

Di sisi lain, menurut Rust Release Blog, tim compiler mengonfirmasi bahwa regresi compile time dari analisis tambahan ini di bawah 2% untuk kebanyakan codebase. Jadi peningkatan akurasi nggak dibayar dengan compile time yang lebih lambat.

Embedded Rust developer debugging borrow checker issues di hardware bare-metal
Developer embedded bisa fokus ke logika hardware, bukan akali borrow checker

Cara Mulai Manfaatkan NLL Baru di Proyek Kamu

  1. Update toolchain: rustup update stable dan pastikan versi >= 1.85.0
  2. Cek ulang kode yang sebelumnya pakai unsafe untuk self-referential pattern. Coba hapus unsafe block-nya dan lihat apakah compiler menerima.
  3. Refactor workaround lama seperti Option::take, RefCell, atau unsafe pointer cast yang dipasang cuma buat menyenangkan borrow checker.
  4. Test di hardware target: embedded code punya surface area bug yang lebih kecil setelah refactor karena unsafe block berkurang.
  5. Kalau masih ditolak, periksa apakah memang borrow-mu overlapping secara temporal. Jangan dipaksa unsafe.

Baca juga pengalaman kami soal memory-safe backend services dengan Rust dan migrasi async_trait ke native AFIT di Rust 1.85 untuk konteks lebih luas soal ekosistem async Rust saat ini.

Pola yang Kini Bisa Tanpa unsafe: Referensi Cepat

PolaSebelum 1.85Rust 1.85
Self-referential borrow di async fnError (false positive)Compile ✅
&self.field + &mut self di yield point bedaErrorCompile ✅
Two-phase borrow di async blockTerbatasFull support ✅
Borrow ke field yang sama, yield point bedaErrorCompile ✅
True overlapping borrow (memang unsafe)ErrorError (tetap ditolak)

FAQ: NLL Rust 1.85 untuk Async Block

Apa itu self-referential struct dan kenapa borrow checker sebelumnya menolaknya di async?

Self-referential struct adalah struct yang salah satu field-nya menyimpan reference ke field lain dalam struct yang sama. Di async block, compiler generate state machine yang penuh implicit move antar yield point. Sebelum 1.85, borrow checker tidak bisa membuktikan bahwa reference internal tidak overlapping dengan mutable borrow di titik yang berbeda secara temporal, jadi langsung menolak meskipun secara runtime aman.

Apakah semua self-referential async block sekarang otomatis compile di Rust 1.85?

Tidak. Hanya pola yang borrow-nya tidak tumpang tindih secara temporal yang kini diterima. Kalau reference dan mutable borrow benar-benar hidup bersamaan di yield point yang sama, compiler tetap menolak. NLL 1.85 mengurangi false positive, bukan menghilangkan semua batasan borrow checking.

Apakah saya masih perlu Pin untuk self-referential struct di Rust 1.85?

Ya, Pin tetap diperlukan untuk memastikan struct tidak dipindah (move) setelah reference internal dibuat. Perbaikan NLL di 1.85 fokus pada analisis borrow sebelum Pin berperan di runtime. Jadi Pin masih jadi bagian wajib dari pola self-referential, tapi kamu tidak perlu unsafe workaround tambahan untuk menyenangkan borrow checker.

Framework apa yang paling diuntungkan dari NLL improvement ini?

Framework async embedded seperti Embassy dan RTIC adalah beneficiary terbesar. Keduanya banyak menggunakan pola self-referential di driver hardware, interrupt handler, dan DMA transfer yang sebelumnya memerlukan unsafe workaround. Library author kini bisa menghapus banyak unsafe block dan menyederhanakan API.

Kesimpulan: Borrow Checker Makin Cerdas, Kode Makin Bersih

Rust 1.85 bukan revolusi, tapi evolusi penting buat siapa pun yang nulis async di level sistem. Borrow checker sekarang cukup pintar membaca maksud kode-mu, bukan cuma struktur statisnya. Buat developer embedded yang udah lama bergulat dengan unsafe workaround di event loop dan interrupt handler, ini sinyal bagus: Rust makin ramah ke bare-metal tanpa kehilangan garansi memory safety-nya.

Kalau kamu nemu pola yang dulu error dan sekarang compile, share di komentar. Kita semua belajar dari battle story masing-masing.

Subscribe newsletter kami buat update mingguan soal Rust systems programming, optimasi compiler, dan trik embedded yang jarang dibahas di tutorial mainstream. Nggak spam, cuma insight yang bikin firmware kamu lebih bersih dan cepat.

About the Author

Dzul Qurnain

Suka nonton Anime, ngoding dan bagi-bagi tips kalau tahu.. Oh iya, suka baca ( tapi yang menarik menurutku aja)... Praktisi WordPress, web development, SEO, dan server administration yang membagikan tutorial teknis dan catatan implementasi nyata.

View All Articles