usize vs u32 : duel des types entiers en Rust
Si vous écrivez en Rust depuis au moins une semaine, vous avez forcément rencontré cette situation : le compilateur râle parce que vous essayez d'utiliser u32 pour indexer un vecteur, alors qu'il exige usize. Ou l'inverse — vous passez un usize à une fonction qui attend un u32, et vous obtenez une vague rouge dans l'IDE.
Sur Stack Overflow, la question « What's the difference between usize and u32 ? » a recueilli des milliers de vues. Ce n'est pas un hasard — la confusion entre ces types est l'un des pièges les plus courants pour les débutants en Rust.
Voyons une bonne fois pour toutes en quoi ils diffèrent, quand utiliser chacun, et comment éviter les erreurs. C'est parti !
En bref : la différence principale
usize est un entier non signé dont la taille dépend de l'architecture du système. Sur un système 64 bits, il fait 8 octets ; sur un système 32 bits, 4 octets. u32 fait toujours 4 octets, quelle que soit la plateforme.
Mais la différence ne réside pas seulement dans la taille. L'essentiel est la sémantique :
usizeest destiné aux tailles, indices et nombres d'éléments dans les collectionsu32est destiné aux données numériques qui ne dépassent pas 4 milliards
Et oui, le compilateur ne permet pas de les mélanger sans conversion explicite. Ce n'est pas un bug, mais une fonctionnalité — ainsi Rust vous protège des erreurs liées à la taille des mots de l'architecture.
Pourquoi usize existe-t-il ?
Imaginez que vous écrivez du code qui doit fonctionner à la fois sur un vieux microcontrôleur 32 bits et sur un serveur 64 bits moderne. La longueur d'un vecteur sur un système 32 bits ne peut pas dépasser 2^32 - 1 éléments, tandis que sur un système 64 bits, elle peut atteindre 2^64 - 1.
Si vous utilisiez u32 pour l'indexation sur un système 64 bits, vous ne pourriez tout simplement pas accéder aux éléments au-delà de 4 milliards — même si la mémoire est suffisante. usize résout ce problème : il peut toujours contenir la taille mémoire maximale possible sur l'architecture actuelle.
// Exemple : usize s'adapte automatiquement à l'architecturefn main() { let large_vec: Vec<u8> = vec![0; 1_000_000_000]; // 1 milliard d'éléments // usize peut contenir cet index sur n'importe quelle plateforme let index: usize = 999_999_999; println!("Élément : {}", large_vec[index]); // En revanche, u32 pourrait ne pas suffire sur un système 32 bits // let bad_index: u32 = 999_999_999; // Sur un système 32 bits, cela compile, mais... // large_vec[bad_index as usize]; // ...il faudra faire une conversion}Comparaison dans un tableau
| Caractéristique | usize | u32 |
|---|---|---|
| Taille sur 64 bits | 8 octets | 4 octets |
| Taille sur 32 bits | 4 octets | 4 octets |
| Plage (64 bits) | 0 .. 2^64-1 | 0 .. 2^32-1 |
| Utilisation principale | Indices, tailles, longueurs | Données numériques |
| Conversion implicite | Non | Non |
Quand utiliser usize ?
Toujours pour l'indexation des collections. C'est une règle quasiment sans exception. Si vous écrivez vec[i], où i est un u32, le compilateur vous dira : « Hé, mon ami, utilisons usize ».
// Correct : on utilise usize pour les indicesfn get_element(vec: &[i32], index: usize) -> Option<&i32> { vec.get(index)}
// Incorrect : u32 pour l'indexation — il faudra convertirfn get_ele