Mis à jour le

🦀
Intermediaire 20 questions • Rust

Questions Rust

20 questions Rust pour les entretiens. Ownership, borrowing, lifetimes, traits et concurrence sans data races.

1. Qu'est-ce que le système d'ownership en Rust et pourquoi est-il important ?

L'ownership est le système central de gestion mémoire de Rust, vérifie à la compilation sans garbage collector. Trois règles : chaque valeur à un seul propriétaire, il ne peut y avoir qu'un propriétaire à la fois, quand le propriétaire sort du scope la valeur est libérée (drop). Le move transfere la propriété (l'ancien propriétaire ne peut plus utiliser la valeur). Le clone fait une copie profonde. Les types implémentant le trait Copy (scalaires, tuples de Copy types) sont copies automatiquement au lieu d'être moves. Ce système elimine les use-after-free, les double-free et les data races à la compilation, sans coût runtime.

2. Expliquez les références et le borrowing en Rust.

Le borrowing permet d'utiliser une valeur sans en prendre la propriété. Deux types : référence partagee (&T, lecture seule, multiples simultanées autorisées) et référence mutable (&mut T, lecture-ecriture, une seule à la fois). La règle fondamentale : on peut avoir soit N références partagees, soit une seule référence mutable, jamais les deux. Le borrow checker du compilateur vérifie ces règles à la compilation. Les lifetimes ('a) annotent la durée de validité des références quand le compilateur ne peut pas l'inferer automatiquement. Elision rules : dans la plupart des cas, les lifetimes sont inferees sans annotation explicite.

3. Qu'est-ce que les traits en Rust et comment fonctionnent-ils ?

Les traits définissent un comportement partage, similaire aux interfaces. Un trait declare des méthodes (avec ou sans implémentation par défaut). Les types implementent les traits avec impl Trait for Type. Les trait bounds contraignent les génériques : fn print<T: Display>(val: T). Syntaxe alternative : fn print(val: impl Display). Les trait objects (dyn Trait) permettent le dispatch dynamique via vtable (polymorphisme runtime). Traits importants de la std : Display, Debug, Clone, Copy, Iterator, From/Into, Deref, Drop. L'orphan rule empêche d'implémenter un trait externe sur un type externe, garantissant la cohérence.

4. Comment fonctionne le pattern matching en Rust ?

Le pattern matching avec match est exhaustif : tous les cas doivent être couverts. Patterns : littéraux, variables, wildcards (_), tuples, structs (déstructuration), enums, références (&), ranges (1..=5), guards (if condition). if let pour un seul pattern, while let pour des boucles. L'enum Option (Some/None) remplace les valeurs null. L'enum Result<T, E> (Ok/Err) gere les erreurs. L'opérateur ? propage automatiquement les erreurs (retourne Err si erreur, deballe Ok sinon). Le pattern matching sur les enums est la manière idiomatique de gérer tous les cas possibles, et le compilateur previent les oublis.

5. Expliquez les smart pointers en Rust.

Les smart pointers ajoutent des fonctionnalités aux références classiques. Box : allocation sur le heap, propriété unique, taille connue à la compilation pour les types recursifs. Rc : référence counting, propriété partagee, single-thread uniquement, immutable. Arc : comme Rc mais thread-safe (atomic référence counting). RefCell : borrow checking au runtime au lieu de la compilation, permet la mutabilite interieure. Mutex : accès mutable exclusif thread-safe. Combinaisons courantes : Arc<Mutex> pour du partage mutable multi-thread, Rc<RefCell> pour du partage mutable single-thread. Weak casse les cycles de références.

6. Comment gérer la concurrence en Rust ?

Rust garantit la sécurité de la concurrence à la compilation. Les traits Send (transferable entre threads) et Sync (accessible depuis plusieurs threads) sont implementes automatiquement. Les threads se créent avec std::thread::spawn. Les channels (mpsc) pour la communication inter-threads. Mutex pour l'exclusion mutuelle, RwLock pour plusieurs lecteurs/un ecrivain. Le crate rayon pour le parallélisme de données simple (par_iter()). Le crate tokio pour l'asynchrone (async/await, tasks, I/O non-bloquant). L'ownership system empêche les data races à la compilation : on ne peut pas partager une référence mutable entre threads sans synchronisation.

7. Qu'est-ce que les lifetimes et quand faut-il les annoter explicitement ?

Les lifetimes representent la durée pendant laquelle une référence est valide. Le compilateur les infere généralement grace aux règles d'elision. Annotations explicites nécessaires quand : une fonction retourne une référence et le compilateur ne peut pas déterminer de quel paramètre elle provient, ou quand une struct contient des références. Syntaxe : fn longest<'a>(x: &'a str, y: &'a str) -> &'a str signifie que la référence retournee vit au moins aussi longtemps que la plus courte des deux entrées. Le lifetime 'static indique que la référence vit pour toute la durée du programme (ex: string literals). Les lifetimes ne changent pas la durée de vie, elles la declarent au compilateur.

8. Expliquez le système de modules et la gestion des crates en Rust.

L'arbre de modules organise le code : crate root (lib.rs ou main.rs) est la racine. mod declare un sous-module (dans un fichier ou un dossier). pub rend les éléments publics. use importe les chemins. Visibilité : par défaut prive, pub(crate) visible dans le crate, pub(super) visible dans le module parent. Les crates sont les unités de compilation : binary (exécutable) ou library. Cargo.toml declare les dépendances. Cargo gere la compilation, les tests, la documentation, et la publication sur crates.io. Les workspaces regroupent plusieurs crates liées. Les features de Cargo permettent la compilation conditionnelle de fonctionnalités optionnelles.

9. Comment Rust gere-t-il l'allocation mémoire sans garbage collector ?

Rust utilise le système RAII (Resource Acquisition Is Initialization). Quand une variable sort du scope, son destructeur (trait Drop) est appele automatiquement, liberant la mémoire. La pile (stack) est utilisée pour les données de taille connue à la compilation. Le tas (heap) via Box, Vec, String, etc. est libère quand le propriétaire est drop. Pas de fragmentation de pause comme avec un GC. Le borrow checker garantit qu'aucune référence ne pointe vers de la mémoire libérée (dangling pointer). Les smart pointers (Rc, Arc) utilisent le référence counting pour la propriété partagee. Unsafe Rust permet de gérer manuellement la mémoire quand nécessaire, mais le programmeur assume la responsabilité.

10. Quelles sont les différences entre les closures et les fonctions en Rust ?

Les closures capturent des variables de leur environnement, contrairement aux fonctions. Trois modes de capture : Fn (capture par référence partagee), FnMut (capture par référence mutable), FnOnce (capture par move, consomme les variables). Le compilateur infere automatiquement le mode le moins restrictif. Le mot-cle move force la capture par valeur (utile pour les threads). Les closures ont un type anonyme unique (chaque closure à son propre type même avec la même signature). On peut les passer comme arguments via les trait bounds (impl Fn(i32) -> i32) ou les trait objects (Box<dyn Fn(i32) -> i32>). Les closures zero-capture peuvent être converties en pointeurs de fonction (fn).

Besoin d'aide pour préparer vos entretiens ?

Decrivez votre profil pour des conseils de preparation personnalises.

Recevoir des conseils

Questions fréquentes

Rust est-il realiste pour un premier emploi ?
Rust est encore niche mais en forte croissance. C'est un excellent atout pour les postes systemes, cloud et blockchain.

Pages liées

Chaque semaine, le meilleur de la tech française

Tendances, salaires, outils et opportunités — directement dans votre boîte mail.

Gratuit. Desabonnement en un clic. Pas de spam.