
Déclarer des variables avec `let` et `mut`
Apprenez à déclarer des variables immuables avec `let` et mutables avec `mut` en Rust. Comprenez l'importance de la mutabilité et du shadowing pour un code sûr et flexible.
La base de la manipulation de données : les variables en Rust
En Rust, comme dans la plupart des langages de programmation, les variables sont des conteneurs nommés qui vous permettent de stocker des données. La manière dont Rust gère la déclaration et la modification des variables est cependant empreinte de sa philosophie axée sur la sécurité et la performance. Ce sous-chapitre se concentre sur les mots-clés fondamentaux `let` et `mut`, qui sont au coeur de la gestion des variables.
Vous allez découvrir que Rust adopte une approche d'immuabilité par défaut, un choix de conception qui peut sembler contraignant au premier abord mais qui apporte des avantages significatifs en termes de prévention des erreurs et de facilité de raisonnement sur le code. Nous explorerons comment déclarer des variables, comment les rendre modifiables lorsque c'est nécessaire, et comment Rust utilise ces concepts pour garantir la robustesse de vos programmes.
Nous aborderons également des concepts liés tels que les constantes et le "shadowing" (masquage), qui offrent des flexibilités supplémentaires tout en respectant les principes de sécurité de Rust. Maîtriser ces aspects est essentiel pour écrire du code Rust idiomatique et efficace.
Le mot-clé `let` : l'immuabilité par défaut
La déclaration de variables en Rust s'effectue principalement avec le mot-clé `let`. Une caractéristique distinctive de Rust est que les variables déclarées avec `let` sont immuables par défaut. Cela signifie qu'une fois qu'une valeur est assignée à une telle variable, elle ne peut plus être changée. Cette approche contraste avec de nombreux autres langages où les variables sont mutables par défaut.
Considérez l'exemple suivant :
fn main() {
let x = 5;
println!("La valeur de x est : {}", x);
// La ligne suivante provoquerait une erreur de compilation :
// x = 6; // error[E0384]: cannot assign twice to immutable variable `x`
// println!("La nouvelle valeur de x serait : {}", x);
}Dans cet exemple, `x` est lié à la valeur `5`. Si vous essayez de réassigner `6` à `x` (la ligne commentée), le compilateur Rust générera une erreur. Ce comportement par défaut encourage l'écriture de code où l'état des variables est plus prévisible, ce qui peut simplifier le débogage et le raisonnement sur les systèmes concurrents.
L'avantage de l'immuabilité par défaut est double. Premièrement, cela vous oblige à réfléchir consciemment aux parties de votre code qui ont besoin de modifier des données, ce qui peut mener à une meilleure conception. Deuxièmement, cela offre des garanties de sécurité, en particulier dans des contextes multithreadés où la modification partagée de données peut entraîner des conditions de concurrence (data races). Rust élimine cette classe de bugs à la compilation en grande partie grâce à ce principe, combiné à son système d'ownership.
Il est important de noter que l'immuabilité s'applique à la liaison (binding) de la variable, pas nécessairement à la donnée elle-même si celle-ci est stockée sur le tas et qu'il existe des mécanismes pour la modifier intérieurement (ce que nous verrons avec les types comme `Cell` ou `Mutex`, mais c'est un sujet plus avancé). Pour les types simples comme les nombres, l'immuabilité de la liaison signifie que la valeur ne peut pas changer.
Le mot-clé `mut` : introduire la mutabilité explicitement
Bien que l'immuabilité par défaut soit bénéfique, il existe de nombreuses situations où vous avez légitimement besoin de modifier la valeur d'une variable. Pour ces cas, Rust fournit le mot-clé `mut` (abréviation de mutable). En ajoutant `mut` devant le nom de la variable lors de sa déclaration, vous indiquez au compilateur et aux autres lecteurs du code que cette variable pourra être réassignée.
Voici comment déclarer et utiliser une variable mutable :
fn main() {
let mut y = 10;
println!("La valeur initiale de y est : {}", y);
y = 20;
println!("La nouvelle valeur de y est : {}", y);
}Dans cet exemple, `y` est déclarée comme mutable. Il est donc permis de changer la valeur de `y` de `10` à `20`. L'utilisation de `mut` est une indication claire de l'intention du programmeur. Cela rend le code plus facile à suivre, car les endroits où l'état peut changer sont explicitement marqués.
Le choix d'utiliser une variable mutable ou immuable dépend du problème que vous essayez de résoudre. Souvent, il est possible de structurer le code de manière à minimiser l'utilisation de variables mutables, par exemple en utilisant des transformations qui créent de nouvelles valeurs plutôt que de modifier des valeurs existantes. Cependant, dans certains algorithmes ou pour des raisons de performance, la mutabilité est la solution la plus directe et la plus efficace.
Rust ne vous pénalise pas en termes de performance pour l'utilisation de variables immuables. Le compilateur est capable d'optimiser le code de manière très efficace, que les variables soient mutables ou immuables. Le choix est donc principalement une question de clarté, de sécurité et de conception du code.
Constantes avec `const` : des valeurs immuables garanties à la compilation
Outre les variables déclarées avec `let`, Rust propose également le concept de constantes. Les constantes sont similaires aux variables immuables, mais avec quelques différences clés. Elles sont déclarées avec le mot-clé `const` au lieu de `let`, et le type de la valeur doit toujours être annoté.
Exemple de déclaration d'une constante :
const MAX_USERS: u32 = 1000;
fn main() {
println!("Le nombre maximum d'utilisateurs est : {}", MAX_USERS);
}Voici les principales caractéristiques et différences des constantes par rapport aux variables `let` :
- Vous n'êtes pas autorisé à utiliser `mut` avec les constantes. Les constantes ne sont pas seulement immuables par défaut, elles sont toujours immuables.
- Les constantes peuvent être déclarées dans n'importe quelle portée, y compris la portée globale, ce qui les rend utiles pour les valeurs qui doivent être partagées à travers différentes parties de votre code.
- Les constantes ne peuvent être initialisées qu'avec une expression constante, c'est-à-dire une expression dont la valeur peut être calculée au moment de la compilation. Elles ne peuvent pas être le résultat d'un appel de fonction ou de toute autre valeur qui ne serait calculée qu'à l'exécution.
- Par convention, les noms de constantes sont écrits en majuscules avec des underscores pour séparer les mots (par exemple, `SECONDS_IN_HOUR`).
Les constantes sont utiles pour des valeurs qui sont connues à l'avance et qui ne changeront jamais pendant toute la durée de vie du programme, comme une constante mathématique (Pi), un seuil fixe, ou une configuration immuable. Le fait que leur valeur soit intégrée directement dans le binaire compilé peut parfois offrir des optimisations de performance.
Le "shadowing" (masquage) : réutiliser des noms de variables
Rust permet un concept appelé shadowing (ou masquage). Cela signifie que vous pouvez déclarer une nouvelle variable avec le même nom qu'une variable existante. La nouvelle variable "masque" alors la précédente. A partir de ce point dans la portée, toute utilisation de ce nom se référera à la nouvelle variable, et la variable précédente n'est plus accessible (bien qu'elle puisse encore exister jusqu'à la fin de sa propre portée).
Voici un exemple de shadowing :
fn main() {
let x = 5;
println!("La valeur de x est : {}", x); // Affiche 5
let x = x + 1; // x est masquée par une nouvelle variable x
println!("La valeur de x après le premier masquage est : {}", x); // Affiche 6
{
let x = x * 2; // Dans cette portée interne, x est à nouveau masquée
println!("La valeur de x dans la portée interne est : {}", x); // Affiche 12
}
println!("La valeur de x après la portée interne est : {}", x); // Affiche 6 (la variable de la portée interne n'existe plus)
}Le shadowing est différent de marquer une variable comme `mut`. Avec `mut`, vous modifiez la valeur d'une même variable, qui doit conserver le même type. Avec le shadowing, vous créez une nouvelle variable. Cela a une conséquence importante : vous pouvez changer le type de la valeur tout en conservant le même nom. Par exemple, vous pourriez vouloir convertir une chaîne de caractères en nombre :
fn main() {
let spaces = " "; // spaces est de type &str (chaîne de caractères)
let spaces = spaces.len(); // spaces est maintenant de type usize (un entier)
println!("Nombre d'espaces : {}", spaces);
}Dans cet exemple, nous avons d'abord une variable `spaces` qui est une chaîne. Ensuite, nous créons une nouvelle variable `spaces` qui stocke la longueur de la chaîne précédente. C'est pratique car cela nous évite d'avoir à inventer des noms différents comme `spaces_str` et `spaces_len`. Le shadowing est souvent utilisé pour effectuer des transformations sur une valeur tout en gardant un nom de variable sémantiquement pertinent.
En résumé, `let` introduit des variables immuables, `mut` permet de les rendre modifiables, `const` définit des valeurs fixes à la compilation, et le shadowing offre une flexibilité pour réutiliser des noms de variables, y compris pour changer leur type. Ces outils, combinés, fournissent une base solide et sûre pour la gestion des données en Rust.