O que é uma string
Em Go, uma string é uma sequência de bytes somente leitura. Não é uma sequência de caracteres, não é uma sequência de code points Unicode — são bytes. A linguagem não garante o que esses bytes representam. Por convenção, e por padrão em todo código-fonte Go, espera-se que os dados de uma string sejam UTF-8 válido, mas o tipo em si não impõe isso.
Concretamente, uma string é uma estrutura de dois campos: um ponteiro para o array de bytes subjacente e um comprimento. É só isso. Sem null terminator, sem campo de capacidade — apenas um ponteiro e uma contagem.
String literals são escritas com aspas duplas. Go também suporta raw string literals delimitados por backticks — eles abrangem múltiplas linhas e ignoram escape sequences:
Indexação e slicing
Como uma string é uma sequência de bytes, a notação de índice retorna um byte — não um caractere:
Você pode extrair uma substring usando uma slice expression. A sintaxe é a mesma que para slices: s[low:high] retorna os bytes do índice low até (mas não incluindo) o índice high:
O resultado ainda é uma string — o slicing não copia os bytes subjacentes. A nova string compartilha memória com a original.
Strings são imutáveis
Uma vez criada, uma string não pode ser modificada. Você pode reatribuir a variável, mas não pode alterar os bytes para os quais a string aponta:
Essa é uma escolha de design deliberada. Como strings são imutáveis, é seguro compartilhá-las — múltiplas variáveis podem apontar para os mesmos bytes subjacentes sem o risco de uma modificar o que a outra vê. Também significa que copiar uma string é barato: você copia o ponteiro e o comprimento, não os bytes.
Para construir uma string modificada, converta para um tipo mutável, faça a alteração e converta de volta:
Conversões entre string, rune e byte
Os três tipos string, rune e byte são intimamente relacionados, e Go permite conversões explícitas entre eles. Cada conversão tem um significado específico.
| Conversão | O que faz |
|---|---|
string(r) onde r é um rune | Cria uma string com a codificação UTF-8 daquele code point |
string(b) onde b é um byte | Cria uma string de um byte contendo aquele valor |
string(n) onde n é um inteiro | Cria uma string com a codificação UTF-8 do code point n |
[]byte(s) | Copia os bytes da string em um novo []byte |
[]rune(s) | Decodifica a string como UTF-8 e retorna cada code point como um rune |
rune(b) | Amplia o valor do byte para um rune |
byte(r) | Trunca o valor do rune para um único byte |
Não é possível converter implicitamente entre esses tipos. Tentar atribuir um valor rune ou byte diretamente a uma variável string — ou passar um onde o outro é esperado — é um compile error:
UTF-8 e Unicode
Arquivos-fonte Go são sempre UTF-8. String literals no seu código-fonte são armazenados como a codificação UTF-8 dos caracteres que você escreveu. Para texto ASCII — letras, dígitos, pontuação — cada caractere ocupa exatamente um byte, então indexar por byte e indexar por caractere é a mesma coisa. Para caracteres não-ASCII, não é.
UTF-8 é uma codificação de largura variável. Um único code point Unicode (um rune na terminologia Go) pode ocupar de 1 a 4 bytes:
- Caracteres ASCII (U+0000 a U+007F): 1 byte
- Caracteres como é, ñ, ü (U+0080 a U+07FF): 2 bytes
- Caracteres CJK e a maior parte do BMP (U+0800 a U+FFFF): 3 bytes
- Emoji e caracteres suplementares (U+10000 em diante): 4 bytes
Isso significa que len(s) retorna o número de bytes, não o número de caracteres. Para uma string com caracteres multi-byte, esses valores diferem:
Indexação retorna bytes, não runes
Como uma string é uma sequência de bytes, s[i] sempre retorna o byte na posição i, não o caractere na posição i. Para strings que contêm caracteres multi-byte, isso produz o valor bruto do byte — não o rune:
Para trabalhar com caracteres em vez de bytes, use []rune:
Iterando sobre uma string com range
O loop for range sobre uma string é consciente de UTF-8. Ele automaticamente decodifica cada code point e fornece o valor rune junto com o byte offset onde aquele rune começa:
Observe que é começa no byte offset 1 e o próximo caractere começa no byte offset 3, porque é ocupa 2 bytes. O for range lida com tudo isso automaticamente — é a forma idiomática de iterar sobre os caracteres de uma string.
O package strings
O package strings fornece o toolkit padrão para trabalhar com strings. Algumas das funções mais usadas:
| Função | O que faz |
|---|---|
strings.Contains(s, substr) | Verifica se substr está contido em s |
strings.HasPrefix(s, prefix) | Verifica se s começa com prefix |
strings.HasSuffix(s, suffix) | Verifica se s termina com suffix |
strings.Count(s, substr) | Conta ocorrências não sobrepostas de substr em s |
strings.Index(s, substr) | Retorna o byte index da primeira ocorrência de substr |
strings.Replace(s, old, new, n) | Substitui as primeiras n ocorrências de old por new; -1 substitui todas |
strings.ToUpper(s) | Retorna s convertida para maiúsculas |
strings.ToLower(s) | Retorna s convertida para minúsculas |
strings.TrimSpace(s) | Retorna s sem espaços em branco no início e no fim |
strings.Split(s, sep) | Divide s em um slice de substrings separadas por sep |
strings.Join(elems, sep) | Une os elementos de um slice com sep entre cada um |
strings.Builder | Buffer eficiente para construir strings incrementalmente |
Para construir strings a partir de muitas partes, use strings.Builder em vez de concatenação — a concatenação com + cria uma nova string a cada operação, enquanto o Builder acumula bytes em um buffer e produz uma string ao final: