Menu

Rune e byte in Golang: le stringhe UTF-8 spiegate

Cosa significano rune e byte in Go, come le stringhe memorizzano UTF-8, perché len conta i byte, come range decodifica le rune, le conversioni tra string, []byte e []rune e l'uso del pacchetto bytes.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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

NomeStesso tipo diLetteraleSignificato
byteuint8byte('A'), 0x41un byte di dati
runeint32'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 pointByteEsempi
da U+0000 a U+007F1ASCII: a, 7, {
da U+0080 a U+07FF2é, ß, ж, ع
da U+0800 a U+FFFF3語, €, 한
da U+10000 in su4emoji, 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

ConversioneRisultatoCosto
[]byte(s)i byte UTF-8copia
[]rune(s)code point decodificatidecodifica e copia (4 byte per rune)
string(b)una stringa dai bytecopia
string(r) dove r è []runecodifica in UTF-8copia
string(r) dove r è una runeuna stringa di un carattereridotto

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 string e usa range quando lo scorri carattere per carattere.
  • Posizioni dei caratteri, inversione, troncamento a n caratteri: converti in []rune.
  • I/O, hashing, codifica, protocolli binari: usa []byte e il pacchetto bytes.
  • Un singolo carattere: una rune. Un singolo byte grezzo: un byte.

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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA