Petunjuk dan cakupan
Anda memelihara layanan Rust 1.85 yang meneruskan callback ke mekanisme retry asinkron. Sebuah callback harus meminjam buffer milik pemanggil, melakukan I/O asinkron, dan bekerja dengan berbagai masa pakai pinjaman (borrow lifetimes). Bandingkan async || {} dengan || async {}, lalu bahas AsyncFn, masa pakai, penangkapan (captures), pembatalan, dan migrasi.
Apa yang diuji oleh pewawancara
- Apakah Anda memahami bahwa future dari async-closure dapat meminjam data yang ditangkap.
- Apakah Anda menggunakan
AsyncFn,AsyncFnMut, danAsyncFnOnceuntuk callback async tingkat tinggi (higher-order). - Apakah Anda membedakan kepemilikan dan masa pakai saat future dibuat, di-poll, atau dibatalkan.
- Apakah Anda menyediakan rencana migrasi versi, pengujian batas, dan pembersihan sumber daya.
Pertanyaan klarifikasi yang perlu diajukan
- Apakah callback dipanggil berulang kali, memutasikan state yang ditangkap, atau dikonsumsi sekali? Itu menentukan pemilihan trait.
- Apakah input dimiliki (owned) atau pinjaman singkat (short borrow)? Haruskah future selesai selama pemanggilan?
- Apakah pembatalan terjadi selama I/O atau saat mekanisme retry keluar? Bagaimana sumber daya eksternal dibersihkan?
- Apakah MSRV telah beralih ke Rust 1.85, dan apakah dependensi mendukung Rust 2024?
Kerangka jawaban 30 detik
|| async {} adalah closure reguler yang mengembalikan blok async; future di dalamnya tidak dapat mengekspresikan hubungan peminjaman yang sama seperti async closure. Rust 1.85 menjadikan async || {} sebagai pemanggilan async kelas satu dan menambahkan trait AsyncFn. Pilih trait berdasarkan pola pemanggilan, nyatakan bahwa pinjaman input berlangsung hingga future selesai, dan biarkan future drop melakukan pembersihan pembatalan. Migrasikan toolchain dan edisi terlebih dahulu, gunakan cargo fix secara konservatif, lalu verifikasi peminjaman, percobaan ulang (retries), dan pembatalan dengan pengujian kompiler serta runtime.
Pembahasan mendalam langkah demi langkah
1. Bandingkan kedua bentuk tersebut
Closure reguler mengembalikan future:
let old = |buf: &mut Vec<u8>| async move { buf.push(1); };Async closure menjadikan pemanggilan asinkron itu sendiri sebagai bagian dari kontrak closure:
let new = async |buf: &mut Vec<u8>| { buf.push(1); };Pertanyaan kuncinya adalah apakah setiap pemanggilan dapat menghasilkan future yang terikat pada input pemanggilan tersebut. Bentuk pertama sering kali membuat batasan masa pakai tingkat tinggi sulit diekspresikan; bentuk kedua diwakili oleh trait AsyncFn.
2. Pilih trait AsyncFn
Gunakan AsyncFn untuk callback read-only yang dapat dipanggil berulang kali, AsyncFnMut saat pemanggilan berulang memutasikan state yang ditangkap, dan AsyncFnOnce saat callback mengonsumsi data tangkapannya. Jangan memaksakan setiap future ke dalam BoxFuture hanya karena sifatnya asinkron; hal itu akan menolak peminjaman singkat yang valid.
3. Masa pakai dan penangkapan (captures)
Pinjaman input harus tetap hidup hingga future selesai atau di-drop. Callback tidak boleh menempatkan pinjaman tersebut ke dalam tugas 'static, dan mekanisme retry tidak boleh mengantrekan future melebihi masa pakai buffer. Untuk pekerjaan latar belakang yang sebenarnya, salin atau pindahkan data yang dimiliki terlebih dahulu sehingga tugas tersebut memiliki masa pakainya sendiri.
4. Pembatalan dan pembersihan
Rust tidak memiliki protokol pembatalan async wajib; future drop biasanya mewakili pembatalan. Wrapper I/O harus menutup soket, melepaskan kunci (locks), dan menghapus file sementara saat di-drop atau melalui token pembatalan eksplisit. Mekanisme retry tidak boleh melakukan polling satu future secara bersamaan atau menggunakan state setelah pembatalan.
5. Percobaan ulang dan efek samping
Hanya operasi idempoten yang boleh dicoba ulang secara otomatis. I/O non-idempoten memerlukan ID permintaan, transaksi, atau kompensasi. Buat future baru untuk setiap percobaan dan catat percobaan, kesalahan, serta alasan pembatalan. Jika efek samping eksternal mungkin telah dilakukan (committed), buat kueri terhadapnya atau gunakan kunci idempotensi sebelum mencoba lagi.
6. Migrasi dan verifikasi
Pindahkan CI, lingkungan pengembangan, dan MSRV ke Rust 1.85, lalu ikuti panduan migrasi Rust 2024. cargo fix --edition bersifat konservatif dan tidak dapat menggantikan peninjauan semantik. Uji peminjaman singkat, penangkapan yang dapat dimutasi (mutable captures), pemanggilan berulang, future drop, batas waktu (timeout), percobaan ulang, dan setiap target yang didukung.
Contoh jawaban berkualitas tinggi
Saya akan memilih AsyncFn, AsyncFnMut, atau AsyncFnOnce sesuai dengan apakah callback berulang dan mengonsumsi atau memutasikan data tangkapan. async || secara langsung mengekspresikan async closure, sehingga future setiap pemanggilan dapat diikat ke pinjaman inputnya; || async {} lebih sulit dibatasi dengan cara ini dalam generik tingkat tinggi. Saya tidak akan menyimpan future pinjaman singkat sebagai 'static; tugas latar belakang menerima data yang dimiliki sebagai gantinya. Future drop atau token pembatalan membersihkan I/O dan kunci. Percobaan ulang dibatasi pada operasi idempoten dan mencatat percobaan serta ID permintaan. Setelah beralih ke Rust 1.85, pengujian kompiler, Miri, dan runtime mencakup masa pakai, percobaan ulang, dan pembatalan.
Kesalahan umum
- Mengklaim bahwa kedua bentuk tersebut identik → mengabaikan semantik captured-borrow dan trait tingkat tinggi → uji callback peminjaman singkat.
- Mewajibkan
'staticuntuk setiap callback → menolak peminjaman latar depan yang valid → pisahkan pemanggilan pinjaman dari data latar belakang yang dimiliki. - Hanya menggunakan flag pembatalan boolean → I/O masih menahan kunci atau soket → sediakan jalur pembersihan drop dan token.
- Mencoba ulang pekerjaan non-idempoten selamanya → menduplikasi efek samping → gunakan ID permintaan, kueri, atau kompensasi.
- Menganggap
cargo fixsebagai keseluruhan migrasi → meninggalkan risiko semantik dan MSRV → tambahkan pengujian lintas target dan pengujian perilaku.
Pertanyaan lanjutan dan tanggapan
Mengapa tidak melakukan box pada setiap callback future sebagai 'static?
Hal itu menghilangkan masa pakai yang terikat pada pinjaman input dan menolak callback peminjaman singkat yang valid. Hanya data yang masuk ke tugas latar belakang berdurasi panjang yang harus dimiliki terlebih dahulu dan kemudian menjadi 'static.
Apa yang terjadi jika callback AsyncFnMut dipanggil secara bersamaan (concurrently)?
Ini mengekspresikan penangkapan yang dapat dimutasi tetapi tidak memberikan keamanan konkurensi. Serialisasikan pemanggilan, tambahkan sinkronisasi, atau berikan state independen untuk setiap pemanggilan; jangan pernah membuat peminjaman mutabel yang tumpang tindih.
Bagaimana Anda membuktikan pembersihan saat future di-drop selama I/O?
Bungkus sumber daya dengan Drop atau jalur pembatalan eksplisit, uji batas waktu dan pembatalan tugas, serta periksa kondisi akhir koneksi, kunci, file sementara, dan ID permintaan eksternal.