Wie man ein Rust-Interview besteht: Top-Fragen für Fortgeschrittene
Rust ist eine der gefragtesten Systemprogrammiersprachen, und die Nachfrage nach Senior-Entwicklern wächst weiter. Wenn Sie bereits die grundlegende Syntax und das Ownership-System beherrschen, erwarten Sie im Interview Fragen, die ein tiefes Verständnis von unsafe-Code, Asynchronität, komplexen Lifetimes und Metaprogrammierung prüfen. In diesem Artikel analysieren wir die wichtigsten Themen und geben Codebeispiele, die Ihnen helfen, das Interview souverän zu bestehen.
1. Unsafe Rust: Wann und warum?
Eine der häufigsten Fragen in fortgeschrittenen Interviews: „Erklären Sie, wann Sie unsafe verwenden würden und welche Garantien dabei verletzt werden.“
Die Antwort sollte die vier Hauptmöglichkeiten umfassen, die unsafe bietet:
- Dereferenzieren von rohen Zeigern (
*const Tund*mut T) - Aufrufen von unsafe-Funktionen (z. B. FFI)
- Zugriff auf veränderliche statische Variablen
- Implementieren von unsafe-Traits
Beispiel: Implementierung eines einfachen dynamischen Arrays mit manueller Speicherverwaltung.
use std::alloc::{alloc, dealloc, Layout};use std::ptr;
struct MyVec { ptr: *mut i32, len: usize, cap: usize,}
impl MyVec { fn new() -> Self { MyVec { ptr: ptr::null_mut(), len: 0, cap: 0, } }
fn push(&mut self, val: i32) { if self.len == self.cap { let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 }; let new_layout = Layout::array::<i32>(new_cap).unwrap(); let new_ptr = unsafe { alloc(new_layout) as *mut i32 }; // alte Daten kopieren unsafe { ptr::copy_nonoverlapping(self.ptr, new_ptr, self.len); if !self.ptr.is_null() { let old_layout = Layout::array::<i32>(self.cap).unwrap(); dealloc(self.ptr as *mut u8, old_layout); } } self.ptr = new_ptr; self.cap = new_cap; } unsafe { ptr::write(self.ptr.add(self.len), val); } self.len += 1; }}
impl Drop for MyVec { fn drop(&mut self) { if !self.ptr.is_null() { unsafe { let layout = Layout::array::<i32>(self.cap).unwrap(); dealloc(self.ptr as *mut u8, layout); } } }}Tipp: Betonen Sie immer, dass unsafe keine Deaktivierung von Prüfungen ist, sondern die Übernahme der Verantwortung für Invarianten.
2. Lifetimes und Varianz: Wie der Compiler Referenzen prüft
Auf fortgeschrittenem Niveau werden Sie gebeten, Varianz (Kovarianz, Kontravarianz, Invarianz) im Kontext von Lifetimes zu erklären. Dies ist der Schlüssel zum Verständnis, warum manche Typen nicht kompilieren.
- Kovarianz:
&'a T– wenn 'a größer als 'b ist, kann&'a Tals&'b Tverwendet werden. - Kontravarianz:
fn(T)– wenn 'a größer als 'b ist, kannfn(&'b T)alsfn(&'a T)verwendet werden. - Invarianz:
&mut T– die Lifetime muss exakt übereinstimmen.
Beispiel: Warum kompiliert der folgende Code nicht?
fn foo<'a>(x: &'a mut &'a str, y: &'a str) { *x = y;}
fn main() { let mut s = "hello"; let r = &s; foo(&mut s, r); // Fehler!}Antwort: &mut T ist invariant bezüglich T, daher muss die Lifetime des inneren Verweises mit der Lifetime des äußeren übereinstimmen. Der Compiler kann die Lifetime von r nicht „verkürzen“.