Introduction : pourquoi la conversion de nombres en Rust n'est pas une blague ?
\nAvez-vous déjà écrit du code en Python et tranquillement fait int_var = 3.14, puis float_var = int_var ? En Python, cela fonctionne car les types sont dynamiques. Mais en Rust, comme chez un horloger exigeant, chaque rouage doit être à sa taille. Et si vous essayez d'insérer un u8 (0–255) dans un u16 — c'est une chose, mais si c'est l'inverse — une "silencieuse" perte de données ou une panique peut survenir.
Sur Stack Overflow, la question « How do I convert between numeric types safely and idiomatically? » a recueilli des milliers de vues. Et ce n'est pas un hasard : les débutants sont souvent perdus entre as, From et TryFrom. Dans cet article, nous allons détailler toutes les méthodes de conversion de nombres en Rust, expliquer quand utiliser laquelle, et montrer des exemples que vous pourrez appliquer immédiatement.
1. Conversion simple avec as : rapide mais dangereuse
\nLa méthode la plus simple est l'opérateur as. Il fonctionne, mais avec des pièges.
let big: u32 = 300;\nlet small: u8 = big as u8; // 300 ne tient pas dans u8 !\nprintln!("{}", small); // Affichage : 44 (300 mod 256)\n\nQue s'est-il passé ? Rust a simplement pris les 8 bits de poids faible du nombre 300 (0b1_0010_1100) et a supprimé le reste, laissant 0b0010_1100 = 44. C'est ce qu'on appelle la troncature (truncation). Pour un u8, cela peut être une surprise.
as n'est sûr que lors de l'élargissement du type (par exemple, u8 → u16), lorsque la valeur tient exactement. Pour le rétrécissement, c'est une bombe à retardement.
Quand utiliser as ? Uniquement lorsque vous êtes absolument certain que la valeur ne dépassera pas les limites du type cible. Par exemple, lors du travail avec des masques de bits ou des protocoles de bas niveau.
2. La méthode idiomatique : les traits From et Into
\nRust est un langage où les erreurs doivent être explicites. C'est pourquoi la bibliothèque standard fournit les traits From et Into pour une conversion sécurisée qui n'échoue jamais.
La règle est simple : si la conversion réussit toujours (pas de perte de données), le trait est implémenté. Par exemple :
\nlet small: u8 = 42;\nlet big: u16 = small.into(); // Into appelle automatiquement From\n// ou\nlet big: u16 = u16::from(small);\nprintln!("{}", big); // 42\n\nAttention : From n'est implémenté que pour les paires où la conversion est garantie sûre. Par exemple, u8 → u16, i32 → f64, bool → i32. Mais u16 → u8 — non, car il peut y avoir un dépassement.
Essayez de compiler ce code :
\nlet big: u16 = 256;\nlet small: u8 = big.into(); // Erreur de compilation !\n\nLe compilateur dira : "the trait `From
3. Quand la conversion peut échouer : TryFrom et TryInto
\nEt si nous voulons tout de même essayer de convertir un u16 en u8, mais avec une vérification ? TryFrom et TryInto viennent à la rescousse. Ils retournent un Result qui doit être traité.
use std::convert::TryFrom;\n\nlet big: u16 = 255;\nmatch u8::try_from(big) {\n Ok(small) => println!("Succès : {}", small),\n Err(e) => println!("Erreur : {}", e),\n}\n// Affichage : Succès : 255\n\nlet big: u16 = 256;\nmatch u8::try_from(big) {\n Ok(small) => println!("Succès : {}", small),\n Err(e) => println!("Erreur : {}", e),\n}\n// Affichage : Erreur : TryFromIntError(())\n\nC'est du Rust idiomatique : vous gérez explicitement l'erreur potentielle. Vous pouvez utiliser unwrap() si vous êtes sûr à 100%, mais il est préférable d'utiliser match