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
| Nome | Mesmo tipo que | Literal | Significado |
|---|---|---|---|
byte | uint8 | byte('A'), 0x41 | um byte de dado |
rune | int32 | '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 points | Bytes | Exemplos |
|---|---|---|
| U+0000 a U+007F | 1 | ASCII: a, 7, { |
| U+0080 a U+07FF | 2 | é, ß, ж, ع |
| U+0800 a U+FFFF | 3 | 語, €, 한 |
| U+10000 em diante | 4 | emoji, 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ão | Resultado | Custo |
|---|---|---|
[]byte(s) | os bytes UTF-8 | copia |
[]rune(s) | code points decodificados | decodifica e copia (4 bytes por rune) |
string(b) | uma string a partir dos bytes | copia |
string(r) com r sendo []rune | codifica em UTF-8 | copia |
string(r) com r sendo uma rune | uma string de um caractere | pequeno |
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
stringe userangepara 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
[]bytee o pacotebytes. - Um único caractere: uma
rune. Um único byte bruto: umbyte.
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.