Aller au contenu

Conversion de types en Rust

Rust ne convertit jamais un type en un autre à votre place : si vous mélangez un i32 et un f64 dans la même expression, le code ne compile pas. Vous devez convertir explicitement. C’est voulu : dans d’autres langages, les conversions silencieuses sont une source fréquente de bugs.

Utilisez le mot-clé as pour convertir (on dit aussi « caster ») un type numérique en un autre :

fn main() {
let x: i32 = 10;
let y: f64 = x as f64; // i32 -> f64
println!("{y}");
}
▶ Essayez par vous-même

C’est plus important qu’il n’y paraît. Divisez deux entiers sans conversion et vous obtiendrez une division entière au lieu du résultat décimal que vous attendiez sans doute :

fn main() {
let a = 10;
let b = 3;
println!("{}", a / b); // 3 (division entière)
println!("{}", a as f64 / b as f64); // 3.3333333333333335
}
▶ Essayez par vous-même

Convertir vers un type plus petit peut perdre des données

Section intitulée « Convertir vers un type plus petit peut perdre des données »

Convertir d’un grand type vers un plus petit (par exemple de i32 vers u8) n’échoue pas : la valeur est simplement tronquée, ce qui peut donner un résultat surprenant :

fn main() {
let big: i32 = 300;
let small = big as u8; // u8 ne contient que 0 à 255
println!("{small}"); // 44, pas 300 : la valeur a « fait le tour »
}
▶ Essayez par vous-même

Convertir des flottants, des caractères et des booléens

Section intitulée « Convertir des flottants, des caractères et des booléens »

as fonctionne avec quelques types de plus que les entiers. Chacun a une règle à connaître :

fn main() {
println!("{}", 3.99_f64 as i32); // 3 : les décimales sont coupées
println!("{}", -3.99_f64 as i32); // -3 : coupé vers zéro, pas vers le bas
println!("{}", 3.99_f64.round() as i32); // 4 : arrondissez d'abord si c'est ce que vous voulez
println!("{}", 300.0_f64 as u8); // 255 : trop grand, donc bloqué au maximum
println!("{}", -5.0_f64 as u8); // 0 : trop petit, donc bloqué au minimum
println!("{}", 'A' as u32); // 65 : le numéro de code d'un char
println!("{}", 97_u8 as char); // a : et dans l'autre sens
println!("{}", true as i32); // 1 (false donnerait 0)
}

Sortie :

3
-3
4
255
0
65
a
1
▶ Essayez par vous-même

À retenir :

  • D’un flottant vers un entier, tout ce qui suit la virgule est supprimé. Il n’y a pas d’arrondi. Appelez .round() d’abord si vous voulez l’entier le plus proche.
  • Les flottants hors limites sont ramenés à la valeur la plus proche qui rentre (300.0 devient 255 dans un u8). C’est différent des entiers, qui « font le tour » (300 est devenu 44 plus haut).
  • Les caractères cachent un nombre (leur point de code Unicode). 'A' as u32 donne 65. Seul un u8 peut être reconverti directement en char ; pour des nombres plus grands, on utilise char::from_u32, car tous les nombres ne correspondent pas à un caractère valide.
  • Les booléens deviennent 1 ou 0. L’inverse ne marche pas : 1 as bool ne compile pas. Utilisez plutôt une comparaison comme n != 0.

La conversion avec as ne fonctionne qu’entre types numériques. Pour transformer du texte en nombre (par exemple quelque chose tapé par l’utilisateur), utilisez .parse() :

fn main() {
let text = "42";
let number: i32 = text.parse().expect("nombre invalide");
println!("{}", number + 8);
}

Sortie :

50
▶ Essayez par vous-même

.parse() peut échouer (et si le texte n’est pas du tout un nombre ?), il renvoie donc un Result plutôt que le nombre directement. Nous n’avons pas encore vu Result : pour l’instant, .expect("message") est une façon simple de dire « plante avec ce message en cas d’échec ». Vous verrez la bonne manière de gérer ce cas sur la page Result.

Dans l’autre sens, c’est plus simple : presque tous les types peuvent devenir une String avec .to_string() :

fn main() {
let number = 42;
let text = number.to_string();
println!("{}", text + "!");
}
▶ Essayez par vous-même

Stockez trois notes d’examen sous forme de i32 : 7, 8 et 10. Affichez leur somme, puis leur moyenne avec deux décimales. Indice : si vous divisez deux entiers, vous obtenez 8 et non 8.33 ; convertissez donc d’abord la somme en f64. {:.2} affiche deux décimales.

Voir la solution
fn main() {
let a: i32 = 7;
let b: i32 = 8;
let c: i32 = 10;
let sum = a + b + c;
let average = sum as f64 / 3.0; // 25.0 / 3.0, et non 25 / 3
println!("somme : {sum}");
println!("moyenne : {:.2}", average);
}

Sortie :

somme : 25
moyenne : 8.33
▶ Essayez par vous-même

Remarquez que Rust affiche les décimales avec un point (8.33), jamais avec une virgule.

Une boutique vous donne le prix sous forme de texte, "4.50", et la quantité sous forme de i32, 3. Transformez le texte en f64, multipliez par la quantité et affichez Total : 13.5. Indice : il vous faut les deux outils de cette page : .parse() pour le texte et as f64 pour la quantité.

Voir la solution
fn main() {
let price_text = "4.50";
let quantity: i32 = 3;
let price: f64 = price_text.parse().expect("prix invalide");
let total = price * quantity as f64; // f64 * f64
println!("Total : {total}");
}

Sortie :

Total : 13.5
▶ Essayez par vous-même

Sans as f64, Rust refuserait de multiplier un f64 par un i32, car il ne mélange jamais les types numériques à votre place. Notez aussi que .parse() attend un point : "4,50" échouerait.

  • Rust ne convertit jamais les types automatiquement : vous convertissez toujours explicitement.
  • Utilisez as pour convertir entre types numériques : x as f64, x as u8.
  • Convertir vers un type plus petit tronque sans prévenir : il n’y a pas d’erreur, alors soyez prudent.
  • D’un flottant vers un entier, les décimales sont coupées (utilisez .round() d’abord pour arrondir). char et bool peuvent aussi être convertis en nombres.
  • Utilisez .parse() pour transformer du texte en nombre, et .to_string() pour l’inverse.

Petit quiz

1. Que se passe-t-il quand on divise deux entiers comme 10 / 3 en Rust, sans conversion ?

2. Que se passe-t-il quand on convertit 300_i32 as u8 ?

3. Que renvoie .parse() quand on convertit une chaîne en nombre, sachant que la conversion peut échouer ?

Score : 0 / 3