Une chaîne Go est une séquence d'octets, et le code source Go comme les littéraux de chaîne sont en UTF-8. Un byte est l'un de ces octets. Une rune est un caractère Unicode (un point de code), que l'UTF-8 stocke sur 1 à 4 octets. La plupart des confusions autour des chaînes en Go viennent du mélange des deux :
語 occupe trois octets en UTF-8, donc "語Go" fait 5 octets mais 3 runes. La boucle à indice voit cinq octets, et les octets 0, 1 et 2 n'ont pas de sens isolément. La boucle range décode l'UTF-8 et fournit trois runes aux indices 0, 3 et 4 : i est toujours une position en octets, pas un nombre de caractères.
byte et rune sont des alias
| Nom | Même type que | Littéral | Signification |
|---|---|---|---|
byte | uint8 | byte('A'), 0x41 | un octet de données |
rune | int32 | 'A', 'é', '世' | un point de code Unicode |
Comme ce sont des alias, un byte est un uint8 et une rune est un int32 ; aucune conversion n'est nécessaire entre l'alias et son type sous-jacent. Les apostrophes créent une rune, les guillemets doubles une chaîne. 'ab' est une erreur de compilation, puisqu'un littéral rune contient exactement un caractère.
Les runes sont des nombres, vous pouvez donc faire de l'arithmétique dessus :
'7' - '0' est la façon classique de transformer un caractère chiffre en sa valeur. Le package unicode classe les runes (IsLetter, IsDigit, IsSpace, IsUpper) et change correctement la casse des lettres non ASCII. %c affiche une rune comme un caractère, %U sous la forme U+4E16, %q comme un littéral rune entre apostrophes.
Comment l'UTF-8 stocke les caractères
| Points de code | Octets | Exemples |
|---|---|---|
| U+0000 à U+007F | 1 | ASCII : a, 7, { |
| U+0080 à U+07FF | 2 | é, ß, ж, ع |
| U+0800 à U+FFFF | 3 | 語, €, 한 |
| U+10000 et au-delà | 4 | emojis, CJK rares |
Le texte ASCII est identique en UTF-8, c'est pourquoi le code basé sur les octets fonctionne souvent en test et casse au premier nom accentué. Le package unicode/utf8 travaille directement avec l'encodage :
% x (avec un espace) affiche les octets en hexadécimal séparés par des espaces. utf8.ValidString indique si une chaîne est de l'UTF-8 valide. Les chaînes Go peuvent contenir n'importe quels octets, pas seulement de l'UTF-8 valide ; quand range rencontre un octet invalide, il fournit utf8.RuneError (U+FFFD, le caractère de remplacement) et avance d'un octet.
Convertir entre string, []byte et []rune
| Conversion | Résultat | Coût |
|---|---|---|
[]byte(s) | les octets UTF-8 | copie |
[]rune(s) | les points de code décodés | décode et copie (4 octets par rune) |
string(b) | une chaîne à partir d'octets | copie |
string(r) où r est un []rune | encode en UTF-8 | copie |
string(r) où r est une rune | une chaîne d'un caractère | faible |
Inverser par octets transformerait 世界 en UTF-8 invalide, c'est pourquoi reverse travaille sur des runes. Même les runes ne racontent pas toute l'histoire : un caractère comme é peut aussi s'écrire e plus un accent combinant, soit deux runes. Pour les caractères visibles par l'utilisateur (clusters de graphèmes), utilisez une bibliothèque comme github.com/rivo/uniseg.
Chaque conversion copie, parce que les chaînes sont immuables et que les slices ne le sont pas. Dans du code critique, évitez les allers-retours ; le compilateur optimise certains cas (comme string(b) utilisé comme clé de map ou dans une comparaison) pour sauter la copie.
Indexer donne un octet
s[i] renvoie un byte, et s[i:j] découpe selon des positions en octets :
s := "café"
fmt.Println(s[3]) // 195: the first byte of é
fmt.Println(string(s[3])) // "Ã": that byte read as a code point
fmt.Println(s[:3]) // "caf"
fmt.Println(s[:4]) // "caf\xc3": half of é, invalid UTF-8
string(s[3]) est un piège : convertir un seul octet en string le traite comme le point de code 195, c'est-à-dire Ã, pas l'octet lui-même. Pour trouver les frontières des caractères, utilisez range, la famille strings.Index (qui renvoie des positions en octets toujours situées sur des frontières), ou utf8.DecodeRuneInString.
Le package bytes
bytes reproduit strings pour les []byte : bytes.Contains, bytes.Split, bytes.Fields, bytes.TrimSpace, bytes.Equal, bytes.ToUpper et ainsi de suite. Utilisez-le quand les données arrivent sous forme d'octets (contenu de fichiers, bodies HTTP, buffers réseau), pour ne pas convertir en chaîne puis revenir :
bytes.Buffer est un buffer d'octets extensible qui implémente io.Reader et io.Writer. C'est le bon outil quand vous écrivez et lisez des octets ; pour construire une chaîne, strings.Builder coûte légèrement moins. Comparez des slices d'octets avec bytes.Equal, puisque == ne fonctionne pas sur les slices.
Choisir entre les deux
- Du texte que vous affichez, comparez ou cherchez : gardez une
string, et utilisezrangequand vous la parcourez caractère par caractère. - Des positions de caractères, une inversion, une troncature à n caractères : convertissez en
[]rune. - E/S, hachage, encodage, protocoles binaires : utilisez des
[]byteet le packagebytes. - Un seul caractère : une
rune. Un seul octet brut : unbyte.
Questions fréquentes
Qu'est-ce qu'une rune en Go ?
rune est un alias de int32 et représente un point de code Unicode. Un littéral rune s'écrit entre apostrophes : 'a' vaut 97, 'é' vaut 233, '世' vaut 19990. Parcourir une chaîne avec for i, r := range s vous donne des runes, décodées à partir des octets UTF-8 de la chaîne.
Quelle est la différence entre une rune et un byte en Go ?
Un byte (alias de uint8) représente 8 bits de données brutes. Une rune (alias de int32) est un caractère Unicode entier. Dans une chaîne UTF-8, les caractères ASCII occupent un octet chacun, tandis que les autres caractères en occupent deux à quatre mais restent une seule rune. len(s) compte les octets ; utf8.RuneCountInString(s) compte les runes.
Comment parcourir les caractères d'une chaîne en Go ?
Utilisez for i, r := range s. Chaque itération donne la position en octets i et la rune r, donc les caractères multi-octets sont gérés correctement. Une simple boucle à indice for i := 0; i < len(s); i++ visite des octets, ce qui découpe les caractères non ASCII en morceaux.
Comment convertir une chaîne en slice d'octets en Go ?
b := []byte(s) copie les octets de la chaîne dans une nouvelle slice. string(b) fait la conversion inverse, là encore avec une copie. Utilisez plutôt []rune(s) quand vous devez travailler sur des caractères, par exemple pour inverser une chaîne ou prendre ses n premiers caractères.