Retour au cours

backend / rust

Types de base et tuples

Leçon 31 exercice

Explication

Ce que vous allez apprendre

  • Choisir la bonne taille d'entier signé ou non signé selon le besoin
  • Comprendre la différence de comportement entre overflow en debug et en release
  • Utiliser checked_add/wrapping_add pour un comportement d'overflow explicite et contrôlé
  • Regrouper des types hétérogènes dans un tuple et le déstructurer
  • Convertir entre types numériques avec as, et connaître son risque de troncature silencieuse

Dans quel contexte ?

Un développeur teste sa fonction de calcul de score en local et tout fonctionne parfaitement. Une fois déployée en mode release (production), un dépassement de capacité (overflow) sur un compteur u8 produit silencieusement une valeur incohérente au lieu de planter comme en développement — un piège qui vient précisément de la différence de comportement entre les deux modes de compilation que cette leçon détaille.

D'abord, il faut choisir consciemment la taille d'un entier

Rust distingue précisément huit tailles d'entiers signés (i8 à i128) et huit tailles non signées (u8 à u128), plus deux tailles dépendantes de la plateforme (isize, usize). Ce dernier type, usize, est particulièrement important : c'est le type utilisé pour indexer les collections, car sa taille correspond exactement à celle d'une adresse mémoire sur la machine cible.

CatégorieExemplesUsage typique
Entiers signési8, i32, i64Valeurs pouvant être négatives
Entiers non signésu8, u32, u64Compteurs, tailles, valeurs jamais négatives
Taille dépendanteusize, isizeIndexation de collections, taille mémoire
Flottantsf32, f64Décimaux (f64 par défaut)

Une fois ce choix fait, il faut comprendre un piège de comportement peu connu

Un dépassement de capacité (par exemple additionner 10 à un u8 qui vaut déjà 250, alors que le maximum est 255) déclenche un panic immédiat en mode debug, mais "boucle" silencieusement (wrap) en mode release — deux comportements radicalement différents pour le même code source.

Prérequis

Il faut être à l'aise avec les variables et la mutabilité (leçon précédente) pour comprendre les exemples de cette leçon.

Il reste une solution pour ne jamais dépendre de ce comportement implicite

checked_add retourne un Option<T> : Some(resultat) si tout va bien, None en cas de dépassement — une gestion explicite et sûre, quel que soit le mode de compilation. wrapping_add, à l'inverse, choisit délibérément le comportement de "wrap" (retour à zéro) partout, sans jamais paniquer.

Piège dangereux

Ne jamais s'appuyer sur le comportement par défaut d'un dépassement d'entier (+, -, * classiques) dans du code destiné à la production, car ce comportement change selon le profil de compilation. Utilise systématiquement checked_* quand un dépassement est possible et doit être détecté, ou wrapping_*/saturating_* quand un comportement précis est voulu intentionnellement.

Enfin, les tuples regroupent des types différents en une seule valeur

let point: (i32, i32, i32) = (10, 20, 30); illustre un tuple à taille fixe, dont chaque élément peut avoir un type différent. La destructuration (let (x, y, z) = point;) extrait chaque valeur dans une variable nommée, bien plus lisible que l'accès positionnel point.0.

Bonne pratique

Utilise as pour une conversion numérique uniquement quand tu es certain que la valeur source rentre dans le type cible. Dans tous les autres cas, préfère TryFrom/TryInto, qui retournent un Result explicite plutôt que de tronquer silencieusement une valeur trop grande, comme le fait as.

Maintenant que tu maîtrises les types de base, il est temps d'aborder le concept qui rend Rust unique parmi tous les langages grand public : l'ownership, sujet de la prochaine leçon.

Commandes & code

Types de base et tuples

Rust est statiquement typé, avec une inférence de type puissante.

rust
fn main() {
    // Entiers : tailles explicites, signes ou non
    let a: i32 = -42;      // entier signe 32 bits (defaut si non precise)
    let b: u8 = 255;       // entier non signe 8 bits
    let c: i64 = 9_000_000_000;
    let d: usize = 10;     // taille dependante de la plateforme, utilise pour les index

    // Flottants
    let e: f64 = 3.14159;  // defaut
    let f: f32 = 2.71;

    // Booleen et caractere (char = 4 octets, un scalaire Unicode)
    let g: bool = true;
    let h: char = 'é';

    // Inference : le type est deduit du contexte
    let i = 42;       // i32 par defaut
    let j = 3.14;      // f64 par defaut

    // Overflow : panique en mode debug, wrap en mode release (comportement different !)
    let k: u8 = 250;
    let l = k.wrapping_add(10); // wrap explicite et controle : 4
    let m = k.checked_add(10);  // retourne Option<u8> : None si overflow
    println!("{} {:?}", l, m);

    // Tuples : regrouper des types differents, taille fixe
    let point: (i32, i32, i32) = (10, 20, 30);
    let (x, y, z) = point; // destructuration
    println!("{} {} {}", x, y, z);
    println!("{}", point.0); // acces par index

    // Tuple unitaire (unit type) : represente "rien", souvent le retour de fonctions
    let rien: () = ();

    // Conversion explicite avec `as` (peut tronquer silencieusement !)
    let grand: i64 = 300;
    let petit = grand as u8; // 44 : troncature, a utiliser avec prudence
    println!("{} {} {} {} {} {} {}", a, b, c, d, e, f, g);
    println!("{}", h);
    println!("{} {}", i, j);
    println!("{}", petit);
    let _ = rien;
}
CatégorieTypes
Entiers signési8, i16, i32, i64, i128, isize
Entiers non signésu8, u16, u32, u64, u128, usize
Flottantsf32, f64
Autresbool, char, () (unit)

Résumé

  • usize/isize s'adaptent à l'architecture (souvent utilisés pour indexer des collections).
  • Un dépassement d'entier panique en mode debug, wrap silencieusement en release : checked_*/wrapping_* rendent le comportement explicite.
  • Un tuple regroupe des types hétérogènes de taille fixe, destructurable avec let (a, b) = t.
  • as convertit mais peut tronquer sans avertissement : préférer TryFrom quand la sécurité compte.

Exercices pratiques

1 disponible
1

Mission : un compteur de stock qui ment en production

Objectif : Diagnostiquer un bug d'overflow silencieux specifique au mode release et le corriger avec les bons outils de conversion.

Contexte

Le service d'inventaire d'un entrepot utilise un u8 pour compter les articles d'un petit casier (max 255 attendu). En debug, tout fonctionne. Une fois deploye en --release, le compteur d'un casier tres actif se met soudainement a afficher des valeurs absurdes proches de zero apres avoir depasse 255 articles recus dans la journee, sans jamais planter ni logger d'erreur. Personne ne comprend pourquoi le bug n'apparaissait jamais en test local.

Résoudre l’exercice →