Menu

Rune e byte em Golang: strings UTF-8 explicadas

O que rune e byte significam em Go, como strings guardam UTF-8, por que len conta bytes, como range decodifica runes, como converter entre string, []byte e []rune e como usar o pacote bytes.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma string em Go é uma sequência de bytes, e o código-fonte Go e os literais de string são UTF-8. Um byte é um desses bytes. Uma rune é um caractere Unicode (um code point), que o UTF-8 guarda em 1 a 4 bytes. A maior parte da confusão com strings em Go vem de misturar os dois:

ocupa três bytes em UTF-8, então "語Go" tem 5 bytes, mas 3 runes. O laço por índice vê cinco bytes, e os bytes 0, 1 e 2 não significam nada sozinhos. O laço com range decodifica o UTF-8 e entrega três runes nos índices 0, 3 e 4: i é sempre um offset em bytes, não uma contagem de caracteres.

byte e rune são aliases

NomeMesmo tipo queLiteralSignificado
byteuint8byte('A'), 0x41um byte de dado
runeint32'A', 'é', '世'um code point Unicode

Como são aliases, um byte é um uint8 e uma rune é um int32; nenhuma conversão é necessária entre o alias e o tipo subjacente. Aspas simples criam uma rune, aspas duplas uma string. 'ab' é erro de compilação, já que um literal de rune guarda exatamente um caractere.

Runes são números, então dá para fazer contas com elas:

'7' - '0' é o jeito clássico de transformar um caractere de dígito no seu valor. O pacote unicode classifica runes (IsLetter, IsDigit, IsSpace, IsUpper) e muda maiúsculas e minúsculas corretamente em letras não ASCII. %c imprime uma rune como caractere, %U no formato U+4E16 e %q como um literal de rune entre aspas.

Como o UTF-8 guarda caracteres

Code pointsBytesExemplos
U+0000 a U+007F1ASCII: a, 7, {
U+0080 a U+07FF2é, ß, ж, ع
U+0800 a U+FFFF3, ,
U+10000 em diante4emoji, CJK raro

Texto ASCII é idêntico em UTF-8, e é por isso que código baseado em bytes muitas vezes funciona nos testes e quebra no primeiro nome com acento. O pacote unicode/utf8 trabalha diretamente com a codificação:

% x (com um espaço) imprime os bytes em hexadecimal separados por espaço. utf8.ValidString diz se uma string é UTF-8 válido. Strings em Go podem guardar quaisquer bytes, não só UTF-8 válido; quando o range encontra um byte inválido, ele entrega utf8.RuneError (U+FFFD, o caractere de substituição) e avança um byte.

Convertendo entre string, []byte e []rune

ConversãoResultadoCusto
[]byte(s)os bytes UTF-8copia
[]rune(s)code points decodificadosdecodifica e copia (4 bytes por rune)
string(b)uma string a partir dos bytescopia
string(r) com r sendo []runecodifica em UTF-8copia
string(r) com r sendo uma runeuma string de um caracterepequeno

Inverter por bytes transformaria 世界 em UTF-8 inválido, e é por isso que reverse trabalha com runes. Nem as runes contam toda a história: um caractere como é também pode ser escrito como e mais um acento combinante, o que dá duas runes. Para os caracteres que o usuário enxerga (grapheme clusters), use uma biblioteca como github.com/rivo/uniseg.

Cada conversão copia, porque strings são imutáveis e slices não. Em código quente, evite converter de um lado para o outro; o compilador otimiza alguns casos (como string(b) usado como chave de map ou em uma comparação) para pular a cópia.

Indexar devolve um byte

s[i] devolve um byte, e s[i:j] fatia por offsets em bytes:

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]) é uma armadilha: converter um único byte para string o trata como o code point 195, que é Ã, e não o próprio byte. Para encontrar as fronteiras entre caracteres, use range, a família strings.Index (que devolve offsets em bytes sempre em fronteiras) ou utf8.DecodeRuneInString.

O pacote bytes

bytes espelha strings para []byte: bytes.Contains, bytes.Split, bytes.Fields, bytes.TrimSpace, bytes.Equal, bytes.ToUpper e assim por diante. Use-o quando os dados chegam como bytes (conteúdo de arquivo, corpo HTTP, buffers de rede), para não converter para string e de volta:

bytes.Buffer é um buffer de bytes que cresce e implementa io.Reader e io.Writer. É a ferramenta certa quando você escreve e lê bytes; para montar uma string, strings.Builder é um pouco mais barato. Compare slices de bytes com bytes.Equal, já que == não funciona com slices.

Qual escolher

  • Texto que você exibe, compara ou busca: mantenha como string e use range para percorrê-lo caractere por caractere.
  • Posições de caractere, inverter, cortar em n caracteres: converta para []rune.
  • I/O, hashing, codificação, protocolos binários: use []byte e o pacote bytes.
  • Um único caractere: uma rune. Um único byte bruto: um byte.

Perguntas frequentes

O que é uma rune em Go?

rune é um alias de int32 e representa um code point Unicode. Um literal de rune fica entre aspas simples: 'a' é 97, 'é' é 233, '世' é 19990. Percorrer uma string com for i, r := range s entrega runes, decodificadas a partir dos bytes UTF-8 da string.

Qual a diferença entre rune e byte em Go?

Um byte (alias de uint8) são 8 bits de dado bruto. Uma rune (alias de int32) é um caractere Unicode inteiro. Em uma string UTF-8, caracteres ASCII ocupam um byte cada, enquanto os outros ocupam de dois a quatro bytes, mas continuam sendo uma rune. len(s) conta bytes; utf8.RuneCountInString(s) conta runes.

Como percorrer os caracteres de uma string em Go?

Use for i, r := range s. Cada iteração entrega o offset em bytes i e a rune r, então caracteres de vários bytes são tratados corretamente. Um laço simples por índice, for i := 0; i < len(s); i++, visita bytes e quebra caracteres não ASCII em pedaços.

Como converter uma string em slice de bytes em Go?

b := []byte(s) copia os bytes da string para um slice novo. string(b) converte de volta, também com cópia. Use []rune(s) quando precisar trabalhar com caracteres, por exemplo para inverter uma string ou pegar os n primeiros caracteres.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR