Gesaan dan Konteks Berkenaan
Sebuah crate Rust gagal dikompilasi selepas beralih daripada Edisi 2021 ke 2024 kerana blok extern sedia ada tidak lagi diterima. Terangkan sebab unsafe extern diperlukan, cara mengaudit pengisytiharan ABI, dan cara membungkus sempadan unsafe dalam API selamat yang boleh disahkan.
Rust 2024 memerlukan blok asing untuk menggunakan kata kunci unsafe. Rust tidak dapat membuktikan tandatangan, konvensyen panggilan, pemboleh ubah global, atau kontrak penunjuk yang dibekalkan oleh pustaka luaran, jadi pengarang pengisytiharan mesti memegang tanggungjawab terhadap andaian tersebut.
Perkara yang Dinilai oleh Penemu Duga
Penemu duga mencari perbezaan antara pengisytiharan unsafe dan API yang mendedahkan unsafe pada setiap tapak panggilan. Anda harus merangkumi ABI, lebar integer, susun atur, penunjuk boleh sifar (nullable pointers), pemilikan, kekangan benang, permulaan, dan modul FFI kecil yang boleh diaudit.
Soalan Penjelasan
Sahkan Edisi crate, platform sasaran, ABI asing, dan versi pengepala. Tanya sama ada fungsi mengembalikan sumber yang dimiliki, penunjuk mana yang mungkin sifar, siapa yang membebaskannya, sama ada panggilan balik merentasi benang, dan sama ada versi pustaka dinamik boleh menyimpang. Pembetulan sintaks sahaja adalah tidak lengkap tanpa jawapan ini.
Rangka Kerja Jawapan 30 Saat
"Rust 2024 menandakan pengisytiharan extern itu sendiri sebagai unsafe kerana pengkompil tidak dapat mengesahkan kontrak ABI asing. Saya akan menulis unsafe extern, mengaudit konvensyen panggilan, susun atur, lebar integer, kesahihan penunjuk, fungsi pemusnahan, dan peraturan benang, serta menandakan fungsi sebagai safe hanya apabila prasyaratnya dibuktikan oleh pembungkus. Saya akan mengekalkan syarat yang tidak dapat dibuktikan sebagai unsafe, kemudian mengesahkan migrasi dengan binaan merentas sasaran dan ujian regresi ABI."
Perbincangan Mendalam Langkah demi Langkah
Langkah 1: Nyatakan tanggungjawab unsafe extern
unsafe extern menyatakan bahawa pengisytiharan boleh membolehkan tingkah laku yang tidak ditentukan dan bahawa pengarang pengisytiharan bertanggungjawab terhadap kontraknya. Ia tidak mengesahkan pelaksanaan C atau menyemak argumen untuk pemanggil. Edisi 2024 menjadikan tanggungjawab itu kelihatan dalam kod sumber.
Langkah 2: Audit ABI dan susun atur
Semak konvensyen panggilan seperti extern "C", susun atur struct, perwakilan enum, penjajaran, lebar integer, dan peraturan nilai pulangan. Pengepala, pengikatan yang dijana, dan pustaka yang dipautkan mesti menerangkan kontrak berversi yang sama; larian tempatan yang berjaya bukanlah bukti merentas sasaran.
Langkah 3: Asingkan fungsi asing yang selamat dan tidak selamat
Fungsi dalam blok asing adalah unsafe secara lalai. Sesuatu fungsi boleh diisytiharkan sebagai safe apabila prasyarat awamnya telah dibuktikan; pemanggil kemudiannya tidak memerlukan blok unsafe, tetapi pengarang pengisytiharan masih memegang tanggungjawab terhadap bukti tersebut. Jangan tandakan fungsi yang tidak diketahui sebagai safe semata-mata untuk mengurangkan sintaks unsafe.
unsafe extern "C" {
safe fn library_version() -> u32;
fn library_parse(ptr: *const u8, len: usize) -> i32;
}Langkah 4: Bungkus penunjuk, pemilikan, dan pemusnahan
Sebelum menukar penunjuk mentah kepada rujukan, sahkan ketidaksifaran, penjajaran, panjang, dan jangka hayat. Sumber yang dikembalikan oleh pustaka asing biasanya mesti dimusnahkan oleh fungsi pembebasan yang sepadan; pemusnah lalai Rust atau peruntuk lain tidak boleh digunakan merentasi sempadan tersebut.
Langkah 5: Semak kekangan benang dan panggilan balik
Tentukan sama ada pemegang boleh merentasi benang, sama ada panggilan balik berjalan pada benang milik pustaka, sama ada panggilan balik boleh masuk semula, dan sama ada pemusnahan menunggu panggilan balik. Jika sifat tersebut tidak dapat dijamin secara statik, kekang model pembenangan pembungkus dan sediakan penghalang penutupan (shutdown barrier).
Langkah 6: Letakkan prasyarat keselamatan dalam pembungkus
Fungsi yang selamat harus menerima jenis Rust yang menyatakan kekangannya, seperti hirisan, enum, atau pemegang yang dimiliki, dan bukannya memaksa setiap pemanggil menghantar penunjuk mentah dan panjang. Pusatkan semakan, hadkan operasi unsafe kepada beberapa baris, dan dokumentasikan serta uji setiap prasyarat.
Langkah 7: Sahkan dengan alat migrasi dan pelbagai sasaran
Jalankan semakan migrasi Edisi dan cargo fix --edition, kemudian semak secara manual perubahan extern yang dijana. CI harus merangkumi platform hos dan sasaran, binaan debug dan release, pemautan statik dan dinamik, serta regresi ABI terhadap versi pustaka sebenar.
Langkah 8: Tangani sisihan versi dan pengunduran (rollback)
Jika pengepala, pengikatan, dan pustaka dinamik tidak sepadan, tetapkan versi (pin) atau jana semula pengikatan dan bukannya menyembunyikan ketidakpadanan dengan penukaran jenis (casts). Lancarkan secara berperingkat, kekalkan laluan pengunduran, dan pantau kegagalan memuatkan, perubahan kod ralat, dan kebocoran sumber.
Contoh Jawapan Berkualiti Tinggi
Saya akan memisahkan semakan pengisytiharan daripada enkapsulasi tapak panggilan. Mula-mula, tukar setiap blok asing kepada unsafe extern "C" dan bandingkan ABI, susun atur, kebolehbatalan, pemilikan, dan fungsi pembebasan dengan versi pengepala dan pustaka yang tepat. Tandakan hanya fungsi dengan prasyarat awam yang terbukti sebagai safe. Kemudian letakkan penunjuk mentah di sebalik modul FFI berasaskan pemegang Rust dan hirisan yang menyemak panjang, permulaan, pembenangan, dan penutupan panggilan balik, serta sentiasa membebaskan sumber dengan fungsi pustaka tersebut. Akhir sekali, gunakan cargo fix --edition untuk perubahan mekanikal, semak perbezaan (diff), dan jalankan ujian ABI, laluan ralat, penutupan serentak, dan keserasian pustaka dinamik pada pelbagai sasaran. Ini mengekalkan unsafe agar sentiasa kelihatan dan boleh diaudit tanpa memaksa setiap pemanggil menghasilkan semula kontrak tersembunyi pustaka asing tersebut.
Kesilapan Biasa
Menambah unsafe pada extern dan berhenti di situ sahaja
Tindakan itu hanya membetulkan sintaks. Tandatangan, susun atur, atau protokol pemusnahan yang salah masih boleh menyebabkan tingkah laku yang tidak ditentukan, jadi semakan kontrak dan regresi masa jalan tetap diperlukan.
Menandakan setiap fungsi asing sebagai selamat
safe adalah jaminan kepada pemanggil, bukan sekadar petunjuk kepada pengkompil. Gunakannya hanya apabila pembungkus dan jenis sentiasa menguatkuasakan prasyarat; fungsi yang tidak diketahui atau mempunyai keadaan global harus kekal sebagai unsafe.
Membebaskan sumber C menggunakan Box milik Rust
Pemusnahan merentas peruntuk boleh merosakkan timbunan (heap). Pustaka yang mencipta sumber mesti menyediakan fungsi pembebasan, dan pelaksanaan Drop pembungkus harus memanggilnya dengan susunan penutupan yang betul.
Soalan Susulan dan Maklum Balas
Bagaimana jika pengepala C tidak mendokumentasikan susun atur struct?
Anggap susun atur sebagai kontrak yang tidak disahkan. Utamakan pengikatan rasmi atau pemegang legap (opaque handles). Jika struct mesti merentasi sempadan, tetapkan versi pengkompil, platform, dan pustaka serta sahkan saiz, penjajaran, dan tingkah laku hujung ke hujung dan bukannya meneka medan.
Bagaimana jika peningkatan pustaka mengubah tingkah laku selepas pengisytiharan selamat dibuat?
Audit semula jaminan selamat untuk setiap peningkatan versi. Tetapkan versi pustaka dan pengikatan serta tambah ujian keserasian untuk kod ralat, pembenangan, dan semantik sumber. Jika prasyarat yang sama tidak lagi dipenuhi, alih keluar safe dan kendalikan keadaan tersebut secara eksplisit dalam pembungkus.
Bagaimanakah API Rust harus mengendalikan panggilan balik yang boleh berjalan pada mana-mana benang?
Jangan hantar penutupan (closure) Rust sembarangan dengan keadaan boleh ubah yang dikongsi secara terus kepada C. Gunakan saluran selamat benang atau pelaksana terkawal, tentukan jangka hayat panggilan balik dan penghalang penutupan, dan dedahkan antara muka yang selamat hanya apabila Send, penyegerakan, dan keperluan jangka hayat dikuatkuasakan.