Pourquoi Rust n'a pas d'arguments par défaut — et comment contourner cela
\n\nSi vous venez à Rust depuis Python, JavaScript ou C++, vous avez probablement été surpris : Rust n'a pas de support intégré pour les arguments par défaut. On ne peut pas simplement écrire fn greet(name = "monde") — le compilateur vous dira « non ».
Mais ne vous découragez pas ! Rust propose d'autres approches, plus flexibles et plus sûres. Dans ce tutoriel, nous allons voir 3 méthodes éprouvées pour implémenter le comportement des arguments par défaut. Vous apprendrez à utiliser Option<T>, les structures de configuration et le pattern Builder — et quelle méthode choisir pour votre tâche.
Cet article est basé sur une question réelle de Stack Overflow (plus de 50 000 vues) — nous l'avons adaptée pour un format éducatif. C'est parti !
\n\nMéthode 1. Utiliser Option<T> — l'option la plus simple
\n\nLa méthode la plus directe consiste à accepter les arguments comme Option<T> et à substituer les valeurs par défaut à l'intérieur de la fonction.
fn greet(name: Option<&str>) {\n let name = name.unwrap_or("monde");\n println!("Bonjour, {} !", name);\n}\n\nfn main() {\n greet(Some("Anna")); // Bonjour, Anna !\n greet(None); // Bonjour, monde !\n}\n\n\nAvantages :
\n- \n
- Code minimal \n
- Compréhensible même pour un débutant \n
- Ne nécessite pas de structures supplémentaires \n
Inconvénients :
\n- \n
- Chaque appel nécessite d'encapsuler dans
Some(...)ouNone\n - Si plusieurs paramètres — le code devient lourd \n
Cette approche convient parfaitement aux fonctions avec 1 à 2 paramètres optionnels. Mais s'ils sont plus nombreux, la lisibilité en souffre.
\n\nMéthode 2. Structure de configuration — propre et évolutive
\n\nLorsque les paramètres deviennent 3, 5 ou 10, une structure de configuration vient à la rescousse. Vous créez un type séparé qui stocke tous les réglages, et vous implémentez le trait Default pour celui-ci.
struct GreetConfig {\n name: String,\n greeting: String,\n excitement_level: u8,\n}\n\nimpl Default for GreetConfig {\n fn default() -> Self {\n Self {\n name: "monde".to_string(),\n greeting: "Bonjour".to_string(),\n excitement_level: 1,\n }\n }\n}\n\nfn greet(config: GreetConfig) {\n let excitement = "!".repeat(config.excitement_level as usize);\n println!("{}, {}{}", config.greeting, config.name, excitement);\n}\n\nfn main() {\n // Utilisation des valeurs par défaut\n greet(GreetConfig::default());\n // Affichage : Bonjour, monde !\n\n // Modification uniquement de ce qui est nécessaire\n greet(GreetConfig {\n name: "Anna".to_string(),\n ..Default::default()\n });\n // Affichage : Bonjour, Anna !\n\n // Appel entièrement personnalisé\n greet(GreetConfig {\n name: "Monde".to_string(),\n greeting: "Salut".to_string(),\n excitement_level: 3,\n });\n // Affichage : Salut, Monde !!!\n}\n\n\nAvantages :
\n- \n
- Flexibilité maximale \n
- Possibilité d'ajouter de nouveaux champs sans modifier la signature de la fonction \n
- La syntaxe
..Default::default()est très lisible \n
Inconvénients :
\n- \n
- Nécessite plus de code boilerplate \n
- Il faut implémenter explicitement le trait
Default\n
C'est le choix idéal pour les projets réels : bibliothèques, fonctions de configuration, API complexes.
\n\nMéthode 3. Pattern Builder — pour ceux qui aiment les chaînes
\n\nLe pattern Builder est un classique de l'écosystème Rust. Vous créez une structure constructeur qui accumule les paramètres, et à la fin, vous appelez la méthode build().
struct Greeter {\n name: String,\n greeting: String,\n excitement_level: u8,\n}\n\nstruct GreeterBuilder {\n nam