Contactez-nous

Types entiers, flottants, booléens et caractères

Plongez dans les types scalaires de Rust : entiers (i32, u64), flottants (f32, f64), booléens (bool) et caractères (char). Maîtrisez leur utilisation pour un code robuste.

Les briques élémentaires de la donnée en Rust : les types scalaires

Au coeur de la représentation de l'information dans tout programme Rust se trouvent les types de données scalaires. Un type scalaire représente une valeur unique et indivisible. Rust, en tant que langage à typage statique, exige que le type de chaque valeur soit connu au moment de la compilation, ce qui garantit la sécurité et permet des optimisations performantes. Ce sous-chapitre explore en détail les quatre principaux types scalaires : les entiers, les nombres à virgule flottante, les booléens et les caractères.

Comprendre ces types fondamentaux est crucial, car ils constituent la base sur laquelle des structures de données plus complexes sont construites. Nous allons examiner les différentes variantes de chaque type, leur taille en mémoire, l'intervalle de valeurs qu'ils peuvent représenter, et comment les utiliser de manière idiomatique en Rust.

Que vous calculiez des âges, des prix, des conditions logiques ou que vous manipuliez du texte, une connaissance approfondie des types scalaires vous permettra d'écrire du code Rust plus précis, plus efficace et moins sujet aux erreurs.

Les types entiers : représenter les nombres sans décimales

Les types entiers sont utilisés pour stocker des nombres qui n'ont pas de partie fractionnaire. Rust offre une gamme variée de types entiers pour s'adapter à différents besoins en termes de taille et de signe. Chaque variante d'entier est définie par sa taille en bits et par le fait qu'elle soit signée (peut représenter des nombres négatifs et positifs) ou non signée (ne peut représenter que des nombres positifs).

Les types entiers signés commencent par la lettre `i` (pour "integer") et les non signés par `u` (pour "unsigned"). Voici un tableau récapitulatif des types entiers disponibles :

  • Signés : `i8`, `i16`, `i32`, `i64`, `i128`, `isize`
  • Non signés : `u8`, `u16`, `u32`, `u64`, `u128`, `usize`

Le nombre après `i` ou `u` indique le nombre de bits que le type utilise. Par exemple, `u8` utilise 8 bits et peut stocker des valeurs de 0 à 28-1 (c'est-à-dire 0 à 255). `i8` utilise également 8 bits mais peut stocker des valeurs de -27 à 27-1 (c'est-à-dire -128 à 127).

Les types `isize` et `usize` sont particuliers : leur taille dépend de l'architecture du système sur lequel le programme est compilé (32 bits sur une architecture 32 bits, 64 bits sur une architecture 64 bits). Ils sont principalement utilisés pour l'indexation de collections, car ils peuvent garantir de pouvoir représenter la taille de n'importe quelle collection en mémoire.

Par défaut, si vous ne spécifiez pas de type pour un littéral entier, Rust l'infère comme `i32`. C'est généralement un bon choix de départ car il est performant et offre une plage de valeurs suffisante pour de nombreux cas d'usage.

fn main() {
    let an_integer = 5; // inféré comme i32
    let a_byte: u8 = 255;
    let small_signed: i8 = -100;
    let large_number: i64 = 1_000_000_000; // Les underscores sont permis pour la lisibilité
    let index: usize = 0;

    println!("Entier par défaut : {}", an_integer);
    println!("Octet : {}", a_byte);
    println!("Petit signé : {}", small_signed);
    println!("Grand nombre : {}", large_number);
    println!("Index : {}", index);
}

Rust gère le dépassement d'entier (integer overflow) de manière spécifique. En mode debug (compilation par défaut avec `cargo build`), un dépassement d'entier provoquera un `panic` (arrêt du programme). En mode release (compilation avec `cargo build --release`), Rust effectue un "two's complement wrapping", c'est-à-dire que si `u8` valant 255 est incrémenté, il deviendra 0. Des méthodes explicites (`wrapping_*`, `saturating_*`, `checked_*`, `overflowing_*`) existent sur les types entiers pour gérer le dépassement de manière contrôlée si ce comportement par défaut n'est pas souhaité.

Les types à virgule flottante : pour les nombres avec décimales

Pour représenter des nombres avec une partie décimale, Rust propose deux types à virgule flottante (souvent appelés "flottants") : `f32` et `f64`. Ces noms indiquent qu'ils utilisent respectivement 32 bits et 64 bits pour leur stockage.

Le type `f32` est un flottant à simple précision, tandis que `f64` est un flottant à double précision. Sur les processeurs modernes, la différence de vitesse de calcul entre `f32` et `f64` est souvent négligeable, voire nulle. Par conséquent, Rust utilise `f64` comme type flottant par défaut, car il offre une plus grande précision. La plupart du temps, `f64` est le bon choix, sauf si vous avez des contraintes spécifiques d'espace mémoire ou si vous interagissez avec du matériel qui ne supporte que la simple précision.

Les types flottants en Rust suivent la norme IEEE 754 pour la représentation des nombres à virgule flottante. Cela signifie qu'ils peuvent représenter des nombres très grands ou très petits, mais avec une précision limitée pour les nombres qui ne peuvent pas être représentés exactement en binaire (comme 0.1).

fn main() {
    let x = 2.0; // inféré comme f64 (par défaut)
    let y: f32 = 3.0; // explicitement f32

    println!("x (f64) = {}", x);
    println!("y (f32) = {}", y);

    // Opérations sur les flottants
    let sum = x + 1.5; // 1.5 est aussi un f64
    let product = y * 2.5; // 2.5 sera traité comme f32 pour correspondre à y

    println!("Somme (f64) = {}", sum);
    println!("Produit (f32) = {}", product);
}

Il est important de noter que les opérations arithmétiques sur des types flottants et des types entiers ne sont pas directement permises sans une conversion explicite (casting). Par exemple, vous ne pouvez pas additionner un `i32` et un `f64` sans convertir l'un des deux. Ceci est une mesure de sécurité de Rust pour éviter les conversions implicites qui pourraient entraîner une perte de précision ou des résultats inattendus.

Le type booléen : pour les valeurs de vérité

Le type booléen en Rust est nommé `bool`. Il ne peut avoir que deux valeurs possibles : `true` (vrai) ou `false` (faux). Les booléens sont essentiels pour la logique conditionnelle, permettant à votre programme de prendre des décisions et d'exécuter différentes branches de code en fonction de certaines conditions.

Les booléens occupent un octet en mémoire. Ils sont le plus souvent utilisés dans les instructions `if`, les boucles `while`, et comme résultat d'opérations de comparaison.

fn main() {
    let is_learning_rust = true;
    let is_weekend: bool = false; // annotation de type optionnelle ici

    if is_learning_rust {
        println!("Super ! Rust est un langage puissant.");
    }

    if !is_weekend { // `!` est l'opérateur de négation logique
        println!("Il faut aller travailler ou étudier !");
    }

    let are_both_true = is_learning_rust && is_weekend; // && est l'opérateur ET logique
    let is_at_least_one_true = is_learning_rust || is_weekend; // || est l'opérateur OU logique

    println!("Les deux sont vrais : {}", are_both_true);
    println!("Au moins un est vrai : {}", is_at_least_one_true);
}

Les opérateurs de comparaison comme `==` (égal à), `!=` (différent de), `<` (inférieur à), `>` (supérieur à), `<=` (inférieur ou égal à), et `>=` (supérieur ou égal à) produisent tous des valeurs booléennes. Ces opérateurs sont fréquemment utilisés pour créer les conditions qui contrôlent le flux de votre programme.

Le type caractère : représenter des symboles Unicode

Le type caractère en Rust, désigné par `char`, est utilisé pour représenter un seul caractère. Une caractéristique importante et puissante du type `char` en Rust est qu'il représente une valeur scalaire Unicode. Cela signifie qu'un `char` peut stocker bien plus que les simples caractères ASCII. Il peut représenter des lettres accentuées, des symboles, des caractères de différentes langues (chinois, japonais, arabe, etc.), et même des emojis.

Les littéraux de type `char` sont spécifiés avec des apostrophes simples (' '), par opposition aux chaînes de caractères qui utilisent des guillemets doubles (" ").

fn main() {
    let c = 'z';
    let z = 'ℤ'; // Caractère mathématique
    let heart_eyed_cat = '😻'; // Emoji
    let pi_symbol: char = 'π'; // Annotation explicite (optionnelle ici)

    println!("Caractère simple : {}", c);
    println!("Caractère mathématique : {}", z);
    println!("Emoji : {}", heart_eyed_cat);
    println!("Symbole Pi : {}", pi_symbol);
}

En raison de sa capacité à représenter n'importe quel caractère Unicode, un `char` en Rust occupe toujours 4 octets en mémoire. C'est une différence notable par rapport à certains langages où un `char` pourrait n'occuper qu'un seul octet (pour ASCII ou Latin-1) ou avoir une taille variable.

Cette gestion robuste de l'Unicode au niveau du type caractère rend Rust particulièrement bien adapté pour développer des applications qui doivent gérer du texte dans un contexte international. Il est important de distinguer le type `char` (un seul caractère) du type `String` ou `&str` (une séquence de caractères), que nous aborderons plus tard. Un `String` est une collection de `char`s, mais la relation n'est pas toujours aussi simple en raison de la complexité de l'encodage UTF-8 utilisé par les chaînes de caractères en Rust.