
Variables, mutabilité et types de données scalaires
Apprenez à déclarer des variables, gérer la mutabilité avec `let` et `mut`, et utiliser les types de données scalaires (entiers, flottants, booléens, caractères) en Rust.
Introduction aux variables et aux types de base en Rust
Au coeur de tout langage de programmation se trouvent les concepts de variables et de types de données. En Rust, ces éléments sont fondamentaux pour écrire du code sûr et performant. Ce chapitre vous guidera à travers la manière dont Rust gère les variables, introduit le concept crucial de mutabilité, et détaille les types de données scalaires, qui sont les briques élémentaires pour représenter des informations simples.
Comprendre comment déclarer une variable, si elle peut être modifiée ou non, et quel type de donnée elle peut contenir est la première étape essentielle pour tout développeur Rust. Nous aborderons la syntaxe spécifique de Rust, tout en expliquant les raisons derrière ses choix de conception, notamment en ce qui concerne la sécurité et la prévention des erreurs courantes.
Vous découvrirez que Rust est un langage à typage statique, ce qui signifie que le type de chaque variable doit être connu au moment de la compilation. Cela permet au compilateur de vérifier la correction de votre code en amont et d'optimiser les performances. Nous explorerons également comment Rust, malgré ce typage statique, offre une certaine flexibilité grâce à l'inférence de type.
Déclarer des variables avec `let` et `mut` : la gestion de la mutabilité
En Rust, les variables sont déclarées avec le mot-clé `let`. Par défaut, les variables ainsi déclarées sont immuables. Cela signifie qu'une fois qu'une valeur est assignée à une variable, vous ne pouvez plus la changer. C'est un choix délibéré qui favorise la sécurité et la prévisibilité du code.
Voici un exemple de déclaration de variable immuable :
fn main() {
let x = 5;
println!("La valeur de x est : {}", x);
// x = 6; // Cela provoquerait une erreur de compilation !
}Si vous avez besoin d'une variable dont la valeur peut changer, Rust vous demande de l'indiquer explicitement en utilisant le mot-clé `mut` après `let`. Cela rend l'intention de modifier la variable claire pour quiconque lit le code.
Exemple de 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);
}Outre les variables, Rust permet de définir des constantes avec le mot-clé `const`. Les constantes sont toujours immuables et leur valeur doit être une expression constante évaluable à la compilation. Leur type doit être explicitement annoté. Par convention, les noms de constantes sont en majuscules avec des underscores pour séparer les mots :
const MAX_POINTS: u32 = 100_000;Rust propose également un concept appelé shadowing (ou masquage). Vous pouvez déclarer une nouvelle variable avec le même nom qu'une variable précédente. Cette nouvelle variable "masque" la précédente. Le shadowing est différent de la mutabilité car il s'agit de la création d'une nouvelle variable. Cela permet notamment de changer le type de la variable tout en conservant le même nom, ce qui peut être pratique.
Exemple de shadowing :
fn main() {
let spaces = " "; // spaces est une chaîne de caractères
let spaces = spaces.len(); // spaces est maintenant un nombre (la longueur de la chaîne précédente)
println!("Il y a {} espaces.", spaces);
}Le shadowing est utile car il évite d'avoir à inventer des noms différents (par exemple `spaces_str` et `spaces_num`) et permet de réutiliser un nom de variable de manière concise après une transformation de sa valeur.
Types entiers, flottants, booléens et caractères : les scalaires en détail
Les types de données scalaires en Rust représentent une valeur unique. Rust propose quatre types scalaires primaires : les entiers, les nombres à virgule flottante, les booléens et les caractères.
Les types entiers sont des nombres sans partie décimale. Rust offre une gamme de types entiers, signés (préfixés par `i` pour "integer") et non signés (préfixés par `u` pour "unsigned"), de différentes tailles : 8, 16, 32, 64 et 128 bits. Par exemple, `u8` peut stocker des valeurs de 0 à 255, tandis que `i8` peut stocker de -128 à 127. Il existe aussi les types `isize` et `usize` dont la taille dépend de l'architecture de la machine cible (32 ou 64 bits).Exemple :
let age: u8 = 30;
let temperature: i32 = -5;
let big_number: i128 = 1_000_000_000_000_000; // Les underscores améliorent la lisibilitéLes types à virgule flottante représentent des nombres avec une partie décimale. Rust a deux types pour cela : `f32` (simple précision) et `f64` (double précision). Le type par défaut est `f64` car sur les processeurs modernes, il est souvent aussi rapide que `f32` mais offre plus de précision.Exemple :
let pi: f64 = 3.1415926535;
let price: f32 = 19.99;Le type booléen, `bool`, ne peut avoir que deux valeurs : `true` ou `false`. Il est principalement utilisé dans les structures de contrôle conditionnelles.Exemple :
let is_rust_fun: bool = true;
let is_raining: bool = false;Le type caractère, `char`, est le type le plus primitif pour représenter des lettres. Les littéraux de type `char` sont spécifiés avec des apostrophes simples. Il est important de noter que `char` en Rust représente une valeur scalaire Unicode, ce qui signifie qu'il peut représenter bien plus que de simples caractères ASCII (comme les emojis, les caractères accentués, etc.). Chaque `char` occupe 4 octets.Exemple :
let initial: char = 'C';
let heart_emoji: char = '💖';
let japanese_char: char = '私';Cette richesse dans la gestion des caractères rend Rust particulièrement adapté aux applications internationalisées.
Opérations de base et inférence de type : manipuler les données efficacement
Rust supporte les opérations mathématiques de base que vous attendez pour les types numériques : addition (`+`), soustraction (`-`), multiplication (`*`), division (`/`) et reste (`%`). Chaque opération nécessite que les opérandes soient du même type.
Exemple d'opérations arithmétiques :
fn main() {
let sum = 5 + 10; // addition
let difference = 95.5 - 4.3; // soustraction
let product = 4 * 30; // multiplication
let quotient = 56.7 / 32.2; // division
let remainder = 43 % 5; // reste
println!("Somme : {}", sum);
println!("Différence : {}", difference);
println!("Produit : {}", product);
println!("Quotient : {}", quotient);
println!("Reste : {}", remainder);
}Pour les booléens, vous pouvez utiliser les opérateurs logiques classiques : `&&` (ET logique), `||` (OU logique), et `!` (NON logique).
L'inférence de type est une caractéristique très pratique de Rust. Dans de nombreux cas, vous n'avez pas besoin de spécifier explicitement le type d'une variable, car le compilateur peut le déduire à partir de la valeur que vous lui assignez et de la manière dont vous l'utilisez. Par exemple, si vous écrivez `let x = 5;`, Rust inférera que `x` est de type `i32` (le type entier par défaut). Si vous écrivez `let y = 2.0;`, `y` sera inféré comme `f64`.
Cependant, il y a des situations où l'inférence de type n'est pas possible ou souhaitable, notamment lorsque plusieurs types pourraient convenir. Par exemple, pour convertir une chaîne de caractères en nombre avec `parse()` :
fn main() {
let guess: u32 = "42".parse().expect("Not a number!");
println!("Votre nombre est : {}", guess);
}Ici, l'annotation de type `: u32` est nécessaire car `parse()` peut convertir une chaîne en de nombreux types numériques différents (`i32`, `u64`, `f32`, etc.). Sans l'annotation, le compilateur ne saurait pas quel type choisir. L'annotation explicite des types peut aussi améliorer la lisibilité de votre code, même quand elle n'est pas strictement requise.
Il est crucial de comprendre que même avec l'inférence de type, Rust reste un langage à typage statique. Le type de chaque variable est déterminé à la compilation et ne peut pas changer au cours de l'exécution. L'inférence ne fait que vous dispenser d'écrire certaines annotations de type, elle ne rend pas le langage dynamiquement typé. Cette combinaison de typage statique fort et d'inférence de type intelligente est l'un des atouts de Rust, offrant à la fois sécurité et confort de développement.