5. Le casting▲
Rust ne permet pas la conversion implicite des types primitifs (coercition). En revanche, une conversion explicite (casting) peut être entreprise à l'aide du mot-clé as
.
Les règles régissant la conversion entre les types littéraux s'inspirent, principalement, des conventions du langage C à l'exception des cas où le C réserve des comportements imprévisibles.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
// Supprime tous les avertissements relatifs aux dépassements
// de capacité (e.g. une variable de type u8 ne peut pas
// contenir plus qu'une variable de type u16).
#![allow(overflowing_literals)]
fn
main() {
let
decimal = 65
.4321_
f32;
// Erreur! La conversion implicite n'est pas supportée.
// let integer: u8 = decimal;
// FIXME ^ Décommentez/Commentez cette ligne pour voir
// le message d'erreur apparaître/disparaître.
// Conversion explicite.
let
integer = decimal as
u8;
let
character = integer as
char;
println!
("Casting:
{}
->
{}
->
{}
"
, decimal, integer, character);
// Lorsque vous convertissez une valeur vers un type
// non-signé T, std::T::MAX + 1 est incrémenté ou soustrait jusqu'à
// ce que la valeur respecte la capacité du nouveau type.
// 1000 ne dépasse pas la capacité d'un entier non-signé codé sur 16 bits.
println!
("1000 as a u16 is:
{}
"
, 1000
as
u16);
// 1000 - 256 - 256 - 256 = 232
// En réalité, les 8 premiers bits les plus faibles (LSB) sont conservés et les
// bits les plus forts (MSB) restants sont tronqués.
println!
("1000 as a u8 is :
{}
"
, 1000
as
u8);
// -1 + 256 = 255
println!
(" -1 as a u8 is :
{}
"
, (-1
i8) as
u8);
// Pour les nombres positifs, cette soustraction est équivalente à une
// division par 256.
println!
("1000 mod 256 is :
{}
"
, 1000
% 256
);
// Quand vous convertissez un type d'entiers signés, le résultat (bit à bit)
// est équivalent à celui de la conversion vers un type d'entiers non-signés.
// Si le bit de poids fort vaut 1, la valeur sera négative.
// Sauf si il n'y a pas de dépassements, évidemment.
println!
(" 128 as a i16 is:
{}
"
, 128
as
i16);
// 128 as u8 -> 128, complément à deux de 128 codé sur 8 bits:
println!
(" 128 as a i8 is :
{}
"
, 128
as
i8);
// On répète l'exemple ci-dessus.
// 1000 as u8 -> 232
println!
("1000 as a i8 is :
{}
"
, 1000
as
i8);
// et le complément à deux de 232 est -24.
println!
(" 232 as a i8 is :
{}
"
, 232
as
i8);
}
5-1. Les littéraux▲
Les littéraux numériques peuvent être typés en suffixant le littéral avec son type. Par exemple, pour préciser que le littéral 42
devrait posséder le type i32, nous écrirons 42
i32.
Le type des littéraux numériques qui ne sont pas suffixés va dépendre du contexte dans lequel ils sont utilisés. S'il n'y a aucune contrainte (i.e. si rien ne force la valeur à être codée sur un nombre de bits bien précis), le compilateur utilisera le type i32 pour les entiers et f64 pour les nombres réels.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
fn
main() {
// Ces littéraux sont suffixés, leurs types sont connus à l'initialisation.
let
x = 1
u8;
let
y = 2
u32;
let
z = 3
f32;
// Ces littéraux ne sont pas suffixés, leurs types dépendent du contexte.
let
i = 1
;
let
f = 1
.0
;
// La fonction `size_of_val` renvoie la taille d'une variable en octets.
println!
("La taille de `x` en octets:
{}
"
, std::mem::size_of_val(&
x));
println!
("La taille de `y` en octets:
{}
"
, std::mem::size_of_val(&
y));
println!
("La taille de `z` en octets:
{}
"
, std::mem::size_of_val(&
z));
println!
("La taille de `i` en octets:
{}
"
, std::mem::size_of_val(&
i));
println!
("La taille de `f` en octets:
{}
"
, std::mem::size_of_val(&
f));
}
Certains concepts présentés dans l'exemple ci-dessus n'ont pas encore été abordés. Pour les plus impatients, voici une courte explication :
- fun(
&
foo) : Cette syntaxe représente le passage d'un paramètre par référence plutôt que par valeur (i.e. fun(foo)). Pour plus d'informations, voir le chapitre du système d'emprunts.
- std::mem::size_of_val est une fonction mais appelée avec son chemin absolu. Le code peut être divisé et organisé en plusieurs briques logiques nommées modules. Pour le cas de la fonction size_of_val, elle se trouve dans le module mem, lui-même se trouvant dans le paquet std. Pour plus d'informations voir les modules et/ou les « crates ».
5-2. L'inférence des types▲
Le moteur dédié à l'inférence des types est très intelligent. Il fait bien plus que d'inférer le type d'une r-value à l'initialisation. Il se charge également d'analyser l'utilisation de la variable dans la suite du programme pour inférer son type définitif. Voici un exemple plus avancé dédié à l'inférence :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
fn
main() {
// Dû à l'annotation(suffixe), le compilateur sait que `elem` possède le type
// u8.
let
elem = 5
u8;
// Crée un vecteur vide (un tableau dont la taille n'est pas définie).
let
mut
vec = Vec::new();
// À ce niveau, le compilateur ne connaît pas encore le type exact de `vec`,
// il sait simplement que c'est un vecteur de quelque chose (`Vec<_>`).
// On ajoute `elem` dans le vecteur.
vec.push(elem);
// Tada! Maintenant le compilateur sait que `vec` est un vecteur
// d'entiers non-signés typés `u8` (`Vec<u8>`).
// TODO ^ Essayez de commenter la ligne où se trouve `vec.push(elem)`.
println!
("
{:?}
"
, vec);
}
Aucun typage explicite n'était nécessaire, le compilateur est heureux et le programmeur aussi !
5-3. Les alias▲
Le mot-clé type
peut être utilisé pour donner un nouveau nom à un type existant. Les types doivent respecter la convention de nommage CamelCase ou le compilateur vous renverra un avertissement. L'exception à cette règle sont les types primitifs : usize, f32, etc.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
// `NanoSecond` est le nouveau nom de `u64`.
type
NanoSecond = u64;
type
Inch = u64;
// Utilisons un attribut pour faire taire les
// avertissements.
#[allow(non_camel_case_types)]
type
u64_t = u64;
// TODO ^ Essayez de supprimer l'attribut.
fn
main() {
// `NanoSecond` = `Inch` = `u64_t` = `u64`.
let
nanoseconds: NanoSecond = 5
as
u64_t;
let
inches: Inch = 2
as
u64_t;
// Notez que les alias de types ne fournissent aucune sécurité supplémentaire,
// car ce ne sont pas de nouveaux types (i.e. vous pouvez très bien changer
// Inch par NanoSecond, vous n'aurez aucune erreur).
println!
("
{}
nanoseconds +
{}
inches =
{}
unit?"
,
nanoseconds,
inches,
nanoseconds + inches);
}
Voir aussi