Una stringa Go è una sequenza di byte, e il codice sorgente Go e i letterali stringa sono in UTF-8. Un byte è uno di quei byte. Una rune è un carattere Unicode (un code point), che UTF-8 memorizza in un numero di byte da 1 a 4. Gran parte della confusione sulle stringhe in Go nasce dal mescolare le due cose:
語 occupa tre byte in UTF-8, quindi "語Go" è lunga 5 byte ma 3 rune. Il ciclo con indice vede cinque byte, e i byte 0, 1 e 2 da soli non hanno senso. Il ciclo range decodifica UTF-8 e produce tre rune agli indici 0, 3 e 4: i è sempre un offset in byte, non un conteggio di caratteri.
byte e rune sono alias
| Nome | Stesso tipo di | Letterale | Significato |
|---|---|---|---|
byte | uint8 | byte('A'), 0x41 | un byte di dati |
rune | int32 | 'A', 'é', '世' | un code point Unicode |
Dato che sono alias, un byte è un uint8 e una rune è un int32; non serve alcuna conversione tra l'alias e il suo tipo sottostante. Gli apici singoli creano una rune, i doppi apici una stringa. 'ab' è un errore di compilazione, perché un letterale rune contiene esattamente un carattere.
Le rune sono numeri, quindi puoi farci dei calcoli:
'7' - '0' è il modo classico per trasformare un carattere cifra nel suo valore. Il pacchetto unicode classifica le rune (IsLetter, IsDigit, IsSpace, IsUpper) e cambia correttamente maiuscole e minuscole anche per le lettere non ASCII. %c stampa una rune come carattere, %U nella forma U+4E16, %q come letterale rune tra apici.
Come UTF-8 memorizza i caratteri
| Code point | Byte | Esempi |
|---|---|---|
| da U+0000 a U+007F | 1 | ASCII: a, 7, { |
| da U+0080 a U+07FF | 2 | é, ß, ж, ع |
| da U+0800 a U+FFFF | 3 | 語, €, 한 |
| da U+10000 in su | 4 | emoji, CJK rari |
Il testo ASCII è identico in UTF-8, ed è per questo che il codice basato sui byte spesso funziona nei test e si rompe al primo nome accentato. Il pacchetto unicode/utf8 lavora direttamente con la codifica:
% x (con uno spazio) stampa i byte in esadecimale separati da spazi. utf8.ValidString indica se una stringa è UTF-8 valido. Le stringhe Go possono contenere qualsiasi byte, non solo UTF-8 valido; quando range incontra un byte non valido produce utf8.RuneError (U+FFFD, il carattere di sostituzione) e avanza di un byte.
Conversioni tra string, []byte e []rune
| Conversione | Risultato | Costo |
|---|---|---|
[]byte(s) | i byte UTF-8 | copia |
[]rune(s) | code point decodificati | decodifica e copia (4 byte per rune) |
string(b) | una stringa dai byte | copia |
string(r) dove r è []rune | codifica in UTF-8 | copia |
string(r) dove r è una rune | una stringa di un carattere | ridotto |
Invertire per byte trasformerebbe 世界 in UTF-8 non valido, ed è per questo che reverse lavora sulle rune. Nemmeno le rune raccontano tutta la storia: un carattere come é si può scrivere anche come e più un accento combinante, cioè due rune. Per i caratteri visibili all'utente (grapheme cluster) usa una libreria come github.com/rivo/uniseg.
Ogni conversione fa una copia, perché le stringhe sono immutabili e gli slice no. Nel codice critico per le prestazioni evita di convertire avanti e indietro; il compilatore ottimizza alcuni casi (come string(b) usato come chiave di una mappa o in un confronto) per saltare la copia.
L'indicizzazione restituisce un byte
s[i] restituisce un byte, e s[i:j] taglia per offset in byte:
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]) è una trappola: convertire un singolo byte in string lo tratta come il code point 195, cioè Ã, non come il byte stesso. Per trovare i confini dei caratteri usa range, la famiglia strings.Index (che restituisce offset in byte sempre su un confine) oppure utf8.DecodeRuneInString.
Il pacchetto bytes
bytes rispecchia strings per []byte: bytes.Contains, bytes.Split, bytes.Fields, bytes.TrimSpace, bytes.Equal, bytes.ToUpper e così via. Usalo quando i dati arrivano come byte (contenuto di file, body HTTP, buffer di rete), così non devi convertire in stringa e poi tornare indietro:
bytes.Buffer è un buffer di byte che cresce e implementa io.Reader e io.Writer. È lo strumento giusto quando devi sia scrivere sia leggere byte; per costruire una stringa strings.Builder costa un po' meno. Confronta gli slice di byte con bytes.Equal, perché == non funziona sugli slice.
Quale scegliere
- Testo che mostri, confronti o cerchi: tienilo come
stringe usarangequando lo scorri carattere per carattere. - Posizioni dei caratteri, inversione, troncamento a n caratteri: converti in
[]rune. - I/O, hashing, codifica, protocolli binari: usa
[]bytee il pacchettobytes. - Un singolo carattere: una
rune. Un singolo byte grezzo: unbyte.
Domande frequenti
Che cos'è una rune in Go?
rune è un alias di int32 e rappresenta un code point Unicode. Un letterale rune si scrive tra apici singoli: 'a' vale 97, 'é' vale 233, '世' vale 19990. Scorrere una stringa con for i, r := range s ti dà delle rune, decodificate dai byte UTF-8 della stringa.
Qual è la differenza tra una rune e un byte in Go?
Un byte (alias di uint8) è formato da 8 bit di dati grezzi. Una rune (alias di int32) è un carattere Unicode intero. In una stringa UTF-8 i caratteri ASCII occupano un byte ciascuno, mentre gli altri caratteri occupano da due a quattro byte ma restano una sola rune. len(s) conta i byte; utf8.RuneCountInString(s) conta le rune.
Come scorro i caratteri di una stringa in Go?
Usa for i, r := range s. Ogni iterazione ti dà l'offset in byte i e la rune r, quindi i caratteri multibyte vengono gestiti correttamente. Un semplice ciclo con indice for i := 0; i < len(s); i++ visita i byte e spezza in pezzi i caratteri non ASCII.
Come converto una stringa in uno slice di byte in Go?
b := []byte(s) copia i byte della stringa in un nuovo slice. string(b) fa la conversione inversa, anche qui con una copia. Usa invece []rune(s) quando devi lavorare con i caratteri, per esempio per invertire una stringa o prenderne i primi n caratteri.