Topik wawancara representatif

Wawancara Rust 2024: Mengapa FFI harus menggunakan unsafe extern?

CodingSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Sebuah crate Rust gagal dikompilasi setelah bermigrasi dari Edisi 2021 ke 2024 karena adanya blok extern. Jelaskan aturan baru ini, cara mengaudit deklarasi C ABI, dan cara membungkus batasan unsafe dalam API aman yang dapat diverifikasi.

Konteks dan Permasalahan

Sebuah crate Rust gagal dikompilasi setelah bermigrasi dari Edisi 2021 ke 2024 karena blok extern yang ada tidak lagi diterima. Jelaskan mengapa unsafe extern diperlukan, cara mengaudit deklarasi ABI, dan cara membungkus batasan unsafe dalam API aman yang dapat diverifikasi.

Rust 2024 mewajibkan blok foreign untuk menggunakan kata kunci unsafe. Rust tidak dapat membuktikan signature, konvensi pemanggilan (calling convention), variabel global, atau kontrak pointer yang disediakan oleh library eksternal, sehingga pembuat deklarasi harus bertanggung jawab penuh atas asumsi-asumsi tersebut.

Hal yang Dievaluasi Pewawancara

Pewawancara mencari pemahaman mengenai perbedaan antara deklarasi unsafe dan API yang mengekspos unsafe di setiap lokasi pemanggilan (call site). Anda harus mencakup ABI, lebar integer, tata letak (layout), pointer nullable, kepemilikan (ownership), batasan thread, inisialisasi, dan modul FFI kecil yang dapat diaudit.

Pertanyaan Klarifikasi

Konfirmasikan Edisi crate, platform target, ABI foreign, dan versi header. Tanyakan apakah fungsi mengembalikan resource yang dimiliki (owned), pointer mana yang boleh null, siapa yang membebaskannya, apakah callback melintasi thread, dan apakah versi dynamic library dapat mengalami pergeseran (drift). Perbaikan yang hanya berfokus pada sintaks tidak lengkap tanpa jawaban-jawaban ini.

Kerangka Jawaban 30 Detik

"Rust 2024 menandai deklarasi extern itu sendiri sebagai unsafe karena kompilator tidak dapat memverifikasi kontrak ABI foreign. Saya akan menulis unsafe extern, mengaudit konvensi pemanggilan, layout, lebar integer, validitas pointer, fungsi destruksi, dan aturan thread, serta menandai suatu fungsi sebagai safe hanya ketika prasyaratnya telah dibuktikan oleh wrapper. Saya akan mempertahankan kondisi yang tidak dapat dibuktikan sebagai unsafe, lalu memvalidasi migrasi dengan cross-target build dan pengujian regresi ABI."

Pembahasan Mendalam Langkah demi Langkah

Langkah 1: Nyatakan tanggung jawab unsafe extern

unsafe extern menyatakan bahwa suatu deklarasi dapat memicu undefined behavior dan pembuat deklarasi bertanggung jawab atas kontraknya. Hal ini tidak memvalidasi implementasi C atau memeriksa argumen bagi pemanggil. Edisi 2024 membuat tanggung jawab tersebut terlihat jelas dalam kode sumber.

Langkah 2: Audit ABI dan layout

Periksa konvensi pemanggilan seperti extern "C", layout struct, representasi enum, perataan (alignment), lebar integer, dan aturan nilai kembalian. Header, binding yang dihasilkan, dan library yang ditautkan harus mendeskripsikan kontrak berversi yang sama; keberhasilan eksekusi di lingkungan lokal bukanlah bukti untuk lintas target.

Langkah 3: Pisahkan fungsi foreign yang aman dan tidak aman

Fungsi di dalam blok foreign secara default bersifat unsafe. Suatu fungsi dapat dideklarasikan sebagai safe jika prasyarat publiknya telah terbukti; dengan demikian pemanggil tidak memerlukan blok unsafe, tetapi pembuat deklarasi tetap bertanggung jawab atas pembuktian tersebut. Jangan menandai fungsi yang tidak dikenal sebagai safe hanya untuk mengurangi sintaks unsafe.

rust
unsafe extern "C" {
    safe fn library_version() -> u32;
    fn library_parse(ptr: *const u8, len: usize) -> i32;
}

Langkah 4: Bungkus pointer, kepemilikan, dan destruksi

Sebelum mengubah raw pointer menjadi referensi, validasi non-nullness, perataan, panjang, dan masa hidup (lifetime). Resource yang dikembalikan oleh foreign library biasanya harus dihancurkan oleh fungsi pembebasan yang sesuai; destruktor bawaan Rust atau allocator lain tidak boleh digunakan melintasi batasan tersebut.

Langkah 5: Periksa batasan thread dan callback

Tentukan apakah handle dapat berpindah antar-thread, apakah callback berjalan pada thread milik library, apakah callback dapat melakukan re-entrancy, dan apakah proses destruksi menunggu callback selesai. Jika properti tersebut tidak dapat dijamin secara statis, batasi model threading pada wrapper dan sediakan shutdown barrier.

Langkah 6: Tempatkan prasyarat keamanan di dalam wrapper

Fungsi yang aman harus menerima tipe data Rust yang mengekspresikan batasannya, seperti slice, enum, atau owned handle, daripada mengharuskan setiap pemanggil meneruskan raw pointer dan panjangnya. Pusatkan pemeriksaan, batasi operasi unsafe hanya pada beberapa baris kode, serta dokumentasikan dan uji setiap prasyarat.

Langkah 7: Validasi dengan alat migrasi dan berbagai target

Jalankan pemeriksaan migrasi Edisi dan cargo fix --edition, lalu tinjau secara manual perubahan extern yang dihasilkan. CI harus mencakup platform host dan target, build debug dan release, penautan statis dan dinamis, serta regresi ABI terhadap versi library yang sebenarnya.

Langkah 8: Tangani pergeseran versi dan rollback

Jika header, binding, dan dynamic library tidak cocok, kunci versi (pin) atau buat ulang binding alih-alih menyembunyikan ketidakcocokan tersebut dengan type casting. Lakukan peluncuran secara bertahap, pertahankan jalur rollback, dan pantau kegagalan pemuatan, perubahan kode kesalahan, serta kebocoran resource.

Contoh Jawaban Berkualitas Tinggi

Saya akan memisahkan peninjauan deklarasi dari enkapsulasi pada call-site. Pertama, ubah setiap blok foreign menjadi unsafe extern "C" dan bandingkan ABI, layout, nullability, kepemilikan, dan fungsi pembebasan dengan versi header dan library yang tepat. Tandai hanya fungsi dengan prasyarat publik yang terbukti sebagai safe. Kemudian letakkan raw pointer di balik modul FFI berbasis Rust handle dan slice yang memeriksa panjang, inisialisasi, threading, dan shutdown callback, serta selalu membebaskan resource dengan fungsi library tersebut. Terakhir, gunakan cargo fix --edition untuk perubahan mekanis, tinjau diff-nya, dan jalankan pengujian ABI, jalur kesalahan, penghentian konkuren, dan kompatibilitas dynamic-library pada beberapa target. Pendekatan ini menjaga unsafe tetap terlihat dan dapat diaudit tanpa memaksa setiap pemanggil mereproduksi kontrak tersembunyi dari foreign library.

Kesalahan Umum

Menambahkan unsafe ke extern lalu berhenti di situ

Langkah tersebut hanya memperbaiki sintaks. Signature, layout, atau protokol destruksi yang salah tetap dapat menyebabkan undefined behavior, sehingga peninjauan kontrak dan regresi runtime tetap diperlukan.

Menandai setiap fungsi foreign sebagai safe

safe adalah jaminan bagi pemanggil, bukan sekadar petunjuk bagi kompilator. Gunakan hanya jika wrapper dan tipe data secara konsisten menegakkan prasyaratnya; fungsi yang tidak dikenal atau memiliki state global harus tetap bersifat unsafe.

Membebaskan resource C menggunakan Box milik Rust

Destruksi lintas allocator dapat merusak heap. Library pembuat harus menyediakan fungsi pembebasan, dan implementasi Drop pada wrapper harus memanggilnya dengan urutan shutdown yang benar.

Pertanyaan Lanjutan dan Tanggapannya

Bagaimana jika header C tidak mendokumentasikan layout struct?

Perlakukan layout sebagai kontrak yang belum diverifikasi. Utamakan binding resmi atau opaque handle. Jika struct harus melintasi batasan FFI, kunci versi kompilator, platform, dan library, lalu verifikasi ukuran, perataan, dan perilaku end-to-end daripada menebak-nebak field-nya.

Bagaimana jika pembaruan library mengubah perilaku setelah deklarasi safe dibuat?

Audit ulang jaminan safe untuk setiap pembaruan versi. Kunci versi library dan binding, lalu tambahkan pengujian kompatibilitas untuk kode kesalahan, threading, dan semantik resource. Jika prasyarat yang sama tidak lagi terpenuhi, hapus safe dan tangani kondisi tersebut secara eksplisit di dalam wrapper.

Bagaimana seharusnya API Rust menangani callback yang dapat berjalan pada thread mana pun?

Jangan meneruskan closure Rust arbitrer dengan shared mutable state secara langsung ke C. Gunakan channel yang thread-safe atau executor yang terkontrol, tentukan masa hidup callback serta shutdown barrier, dan ekspos antarmuka yang aman hanya ketika Send, sinkronisasi, dan persyaratan masa hidup telah ditegakkan.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Tangkapan Layar untuk perintah coding

Ambil tangkapan layar soal, lalu telusuri batasan, solusi, kode, edge case, dan kompleksitas secara berurutan.

Lihat alat