Comment réussir un entretien Rust : les meilleures questions de niveau avancé
Rust est l'un des langages de programmation système les plus demandés, et la demande de développeurs senior continue de croître. Si vous maîtrisez déjà la syntaxe de base et le système de possession, les entretiens vous poseront des questions qui testent une compréhension approfondie du code unsafe, de l'asynchrone, des durées de vie complexes et de la métaprogrammation. Dans cet article, nous analyserons les sujets clés et fournirons des exemples de code pour vous aider à réussir votre entretien en toute confiance.
1. Unsafe Rust : quand et pourquoi ?
L'une des questions les plus fréquentes lors d'un entretien avancé : « Expliquez quand vous utiliseriez unsafe et quelles garanties sont alors violées ».
La réponse doit inclure les quatre principales capacités offertes par unsafe :
- Déréférencement de pointeurs bruts (
*const Tet*mut T) - Appel de fonctions unsafe (par exemple, FFI)
- Accès aux variables statiques mutables
- Implémentation de traits unsafe
Exemple : implémentation d'un tableau dynamique simple avec gestion manuelle de la mémoire.
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 }; // copie des anciennes données 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); } } }}Conseil : soulignez toujours que unsafe n'est pas une désactivation des vérifications, mais une prise de responsabilité pour les invariants.
2. Lifetimes et variance : comment le compilateur vérifie les références
Au niveau avancé, on vous demandera d'expliquer ce qu'est la variance (covariance, contravariance, invariance) dans le contexte des durées de vie. C'est la clé pour comprendre pourquoi certains types ne compilent pas.
- Covariance :
&'a T— si 'a est plus grande que 'b, alors&'a Tpeut être utilisé comme&'b T. - Contravariance :
fn(T)— si 'a est plus grande que 'b, alorsfn(&'b T)peut être utilisé commefn(&'a T). - Invariance :
&mut T— la durée de vie doit correspondre exactement.
Exemple : pourquoi le code suivant ne compile-t-il pas ?
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); // erreur !}Réponse : &mut T est invariant par rapport à T, donc la durée de vie de la référence interne doit correspondre à celle de la référence externe. Le compilateur ne peut pas « réduire » la durée de vie de r.