Gesaan dan skop
Anda menyelenggara perkhidmatan Rust 1.85 yang menghantar callback kepada mekanisme cuba semula tak segerak (asynchronous retryer). Callback mesti meminjam penimbal milik pemanggil, melaksanakan I/O tak segerak dan berfungsi dengan jangka hayat pinjaman yang berbeza. Bandingkan async || {} dengan || async {}, kemudian bincangkan AsyncFn, jangka hayat, tangkapan (captures), pembatalan dan migrasi.
Perkara yang diuji oleh penemu duga
- Sama ada anda memahami bahawa future bagi async-closure boleh meminjam data yang ditangkap.
- Sama ada anda menggunakan
AsyncFn,AsyncFnMutdanAsyncFnOnceuntuk callback tak segerak peringkat lebih tinggi (higher-order). - Sama ada anda membezakan pemilikan dan jangka hayat apabila future dicipta, ditinjau (polled) atau dibatalkan.
- Sama ada anda menyediakan pelan migrasi versi, ujian sempadan dan pembersihan sumber.
Soalan penjelasan untuk ditanya
- Adakah callback dipanggil berulang kali, mengubah keadaan yang ditangkap, atau digunakan sekali sahaja? Itu menentukan pemilihan trait.
- Adakah input dimiliki atau pinjaman pendek? Adakah future mesti selesai semasa panggilan?
- Adakah pembatalan berlaku semasa I/O atau apabila percubaan semula keluar? Bagaimanakah sumber luaran dibersihkan?
- Adakah MSRV telah dipindahkan ke Rust 1.85, dan adakah kebergantungan menyokong Rust 2024?
Rangka kerja jawapan 30 saat
|| async {} ialah closure biasa yang mengembalikan blok async; future dalamannya tidak dapat menyatakan hubungan peminjaman yang sama seperti async closure. Rust 1.85 menjadikan async || {} sebagai panggilan async kelas pertama dan menambah trait AsyncFn. Pilih trait mengikut corak panggilan, nyatakan bahawa pinjaman input kekal sehingga future selesai, dan biarkan future drop melakukan pembersihan pembatalan. Migrasikan toolchain dan edisi terlebih dahulu, gunakan cargo fix secara konservatif, kemudian sahkan peminjaman, percubaan semula dan pembatalan dengan ujian pengkompil dan masa jalanan (runtime).
Penerangan mendalam langkah demi langkah
1. Bandingkan dua bentuk tersebut
Closure biasa mengembalikan future:
let old = |buf: &mut Vec<u8>| async move { buf.push(1); };Async closure menjadikan panggilan tak segerak itu sendiri sebagai sebahagian daripada kontrak closure:
let new = async |buf: &mut Vec<u8>| { buf.push(1); };Soalan penting ialah sama ada setiap panggilan boleh menghasilkan future yang terikat pada input panggilan tersebut. Bentuk pertama sering menyebabkan kekangan jangka hayat peringkat lebih tinggi sukar dinyatakan; bentuk kedua diwakili oleh trait AsyncFn.
2. Pilih trait AsyncFn
Gunakan AsyncFn untuk callback baca sahaja yang boleh dipanggil berulang kali, AsyncFnMut apabila panggilan berulang mengubah keadaan yang ditangkap, dan AsyncFnOnce apabila callback menggunakan tangkapannya. Jangan paksa setiap future menjadi BoxFuture semata-mata kerana ia tak segerak; tindakan itu akan menolak pinjaman pendek yang sah.
3. Jangka hayat dan tangkapan
Pinjaman input mesti hidup sehingga future selesai atau di-drop. Callback tidak boleh meletakkan pinjaman tersebut dalam tugas 'static, dan mekanisme cuba semula tidak boleh memasukkan future ke dalam giliran melebihi jangka hayat penimbal. Untuk kerja latar belakang sebenar, salin atau pindahkan data yang dimiliki terlebih dahulu supaya tugas tersebut memiliki jangka hayatnya sendiri.
4. Pembatalan dan pembersihan
Rust tidak mempunyai protokol pembatalan tak segerak yang wajib; future drop biasanya mewakili pembatalan. Pembalut I/O harus menutup soket, melepaskan kunci (locks) dan memadam fail sementara semasa drop atau melalui token pembatalan yang jelas. Mekanisme cuba semula tidak boleh meninjau satu future secara serentak atau menggunakan keadaan selepas pembatalan.
5. Percubaan semula dan kesan sampingan
Hanya operasi idempoten yang perlu dicuba semula secara automatik. I/O bukan idempoten memerlukan ID permintaan, transaksi atau pampasan. Cipta future baharu untuk setiap percubaan dan rekod percubaan, ralat dan sebab pembatalan. Jika kesan sampingan luaran mungkin telah dilakukan (committed), buat pertanyaan mengenainya atau gunakan kunci keidempotennan sebelum mencuba semula.
6. Migrasi dan pengesahan
Pindahkan CI, persekitaran pembangunan dan MSRV ke Rust 1.85, kemudian ikuti panduan migrasi Rust 2024. cargo fix --edition adalah konservatif dan tidak boleh menggantikan semakan semantik. Uji pinjaman pendek, tangkapan boleh ubah (mutable captures), panggilan berulang, future drop, had masa tamat (timeout), percubaan semula dan setiap sasaran yang disokong.
Contoh jawapan berkualiti tinggi
Saya akan memilih AsyncFn, AsyncFnMut atau AsyncFnOnce mengikut sama ada callback berulang dan menggunakan atau mengubah tangkapan. async || menyatakan async closure secara langsung, jadi future bagi setiap panggilan boleh diikat pada pinjaman inputnya; || async {} lebih sukar dikekang dengan cara ini dalam generik peringkat lebih tinggi. Saya tidak akan menyimpan future pinjaman pendek sebagai 'static; tugas latar belakang menerima data yang dimiliki sebaliknya. Future drop atau token pembatalan membersihkan I/O dan kunci. Percubaan semula dihadkan kepada operasi idempoten dan merekodkan percubaan serta ID permintaan. Selepas beralih ke Rust 1.85, pengkompil, Miri dan ujian masa jalanan merangkumi jangka hayat, percubaan semula dan pembatalan.
Kesilapan biasa
- Mendakwa bentuk tersebut adalah sama → mengabaikan semantik captured-borrow dan trait peringkat lebih tinggi → uji callback pinjaman pendek.
- Memerlukan
'staticuntuk setiap callback → menolak pinjaman latar hadapan yang sah → asingkan panggilan yang dipinjam daripada data latar belakang yang dimiliki. - Hanya menggunakan bendera pembatalan boolean → I/O masih memegang kunci atau soket → sediakan laluan pembersihan drop dan token.
- Mencuba semula kerja bukan idempoten selama-lamanya → menduplikasi kesan sampingan → gunakan ID permintaan, pertanyaan atau pampasan.
- Menganggap
cargo fixsebagai keseluruhan migrasi → meninggalkan risiko semantik dan MSRV → tambah ujian rentas sasaran dan tingkah laku.
Soalan susulan dan jawapan
Mengapa tidak meletakkan setiap future callback ke dalam Box sebagai 'static?
Tindakan itu menghilangkan jangka hayat yang terikat pada pinjaman input dan menolak callback pinjaman pendek yang sah. Hanya data yang memasuki tugas latar belakang yang berpanjangan harus dimiliki terlebih dahulu dan kemudian menjadi 'static.
Apakah yang berlaku jika callback AsyncFnMut dipanggil secara serentak?
Ia menyatakan tangkapan boleh ubah tetapi tidak memberikan keselamatan keserentakan. Susun panggilan secara bersiri, tambah penyegerakan, atau berikan keadaan bebas kepada setiap panggilan; jangan sekali-kali membuat pinjaman boleh ubah yang bertindih.
Bagaimanakah anda membuktikan pembersihan apabila future di-drop semasa I/O?
Balut sumber dengan Drop atau laluan pembatalan yang jelas, uji had masa tamat dan pembatalan tugas, serta periksa keadaan akhir sambungan, kunci, fail sementara dan ID permintaan luaran.