Introduction : Une petite lettre, une grande différence
\n\nBonjour, futur développeur Rust ! Si tu as déjà commencé à étudier Rust, tu as sûrement remarqué que le compilateur est un professeur strict mais juste. Il ne pardonne pas les fautes de frappe, et encore moins la confusion dans la syntaxe. L'un des pièges les plus fréquents dans lequel tombent les débutants (et pas seulement) est la confusion entre self et Self. Il semblerait que la différence ne soit qu'une seule lettre – majuscule ou minuscule. Mais derrière cette lettre se cache une différence fondamentale entre l'instance et le type.
Sur Stack Overflow, cette question a recueilli des milliers de vues, et ce n'est pas un hasard. Aujourd'hui, nous allons une fois pour toutes comprendre la différence entre self et Self en Rust. Nous ne nous contenterons pas de lire une définition ennuyeuse, mais nous suivrons un tutoriel pas à pas avec des exemples concrets, en écrivant quelques petits programmes. À la fin de l'article, tu ne connaîtras pas seulement la différence, mais tu la ressentiras intuitivement. C'est parti !
1. self : « Moi » – c'est à propos de l'instance
\n\nCommençons par self. En Rust, comme dans de nombreux autres langages, self est utilisé pour désigner l'instance actuelle d'une structure ou d'une énumération. C'est l'équivalent de this en C++, Java, JavaScript ou Python. Lorsque tu écris une méthode qui est appelée sur un objet spécifique, tu utilises self.
self a trois formes principales dans la signature de la méthode :
- \n
self– prend possession de l'instance (consumes the instance). Après l'appel de la méthode, l'objet d'origine devient inaccessible. \n &self– une référence immuable à l'instance (le cas le plus courant). La méthode peut lire les données, mais ne peut pas les modifier. \n &mut self– une référence mutable à l'instance. La méthode peut modifier les champs de la structure. \n
Écrivons un exemple simple. Imagine que nous créons une structure Étudiant.
struct Étudiant {\n nom: String,\n année: u32,\n}\n\nimpl Étudiant {\n // Méthode qui prend &self (référence immuable)\n fn se_présenter(&self) {\n println!("Bonjour ! Je suis {}, j'étudie en {}ème année.", self.nom, self.année);\n }\n\n // Méthode qui prend &mut self (référence mutable)\n fn passer_à_l_année_suivante(&mut self) {\n self.année += 1;\n println!("{} est maintenant en {}ème année !", self.nom, self.année);\n }\n\n // Méthode qui prend self (possession)\n fn terminer_études(self) {\n println!("{} a terminé ses études en {}ème année. Adieu !", self.nom, self.année);\n // self est détruit ici, la mémoire est libérée\n }\n}\n\nfn main() {\n let mut étudiant = Étudiant {\n nom: String::from("Anna"),\n année: 2,\n };\n\n étudiant.se_présenter(); // &self\n étudiant.passer_à_l_année_suivante(); // &mut self\n étudiant.terminer_études(); // self (possession)\n\n // Après l'appel de terminer_études, la variable 'étudiant' n'est plus accessible !\n // Décommente la ligne suivante pour voir l'erreur de compilation :\n // étudiant.se_présenter(); // ERREUR : borrow of moved value\n}\n\nFais attention au dernier commentaire. Lorsque nous passons self (sans référence), nous cédons la possession. C'est un outil puissant qui permet de contrôler précisément la durée de vie d'un objet. Dans d'autres langages (par exemple, Python), un tel contrôle n'existe pas – l'objet vit tant qu'il y a des références. En Rust, tu décides toi-même quand l'objet sera « consommé ».
2. Self : « Moi » – c'est à propos du type
\n\nEt maintenant, le plus intéressant. Self (avec une majuscule) est un alias pour le type pour lequel nous implémentons des méthodes. Plus simplement, Self à l'intérieur du bloc impl est remplacé par le nom de la structure ou de l'énumération avec laquelle nous travaillons.
Pourquoi est-ce nécessaire ? Premièrement, c'est pratique. Au lieu d'écrire le nom complet de la structure, tu écris Self. Deuxièmement, c'est criti