O que é String em Go?
Uma string em Go é uma sequência imutável de bytes. Internamente, uma string é representada como um header de dois campos: um ponteiro para os dados e um inteiro com o comprimento em bytes. Essa simplicidade torna strings extremamente eficientes para passar entre funções — apenas 16 bytes são copiados, independente do tamanho do texto.
Um ponto fundamental que diferencia Go de muitas outras linguagens: strings em Go são sequências de bytes, não de caracteres. Go usa UTF-8 como encoding padrão (projetado por Ken Thompson e Rob Pike, que também criaram Go), e cada caractere pode ocupar de 1 a 4 bytes. Essa distinção entre bytes e caracteres (chamados runes em Go) é essencial para trabalhar corretamente com texto Unicode.
A imutabilidade de strings garante segurança em ambientes concorrentes — múltiplas goroutines podem compartilhar a mesma string sem risco de data race, sem necessidade de mutex ou outros mecanismos de sincronização.
Criando strings
String literals
Go oferece duas formas de criar strings literais:
// String interpretada — processa escape sequences
saudacao := "Olá, mundo!\n"
tabulada := "Nome:\tGo"
aspas := "Ele disse \"olá\""
// Raw string — literal, sem processamento de escapes
regex := `\d{3}\.\d{3}\.\d{3}-\d{2}`
multiLinha := `
SELECT id, nome, email
FROM usuarios
WHERE ativo = true
ORDER BY nome
`
json := `{"nome": "Go", "versao": "1.22"}`
Raw strings (com crases) são extremamente úteis para expressões regulares, queries SQL, JSON templates e qualquer texto que contenha barras invertidas ou múltiplas linhas.
String vazia e zero value
var s string // zero value: "" (string vazia)
vazia := "" // explicitamente vazia
fmt.Println(s == "") // true
fmt.Println(len(s)) // 0
Strings são imutáveis
Uma vez criada, uma string não pode ser modificada:
s := "Hello"
// s[0] = 'h' // ERRO de compilação: cannot assign to s[0]
// Para "modificar", crie uma nova string
s = "hello" // reatribuição — cria nova string
Isso significa que operações como concatenação sempre criam novas strings, o que pode impactar a performance em loops com muitas concatenações.
String vs []byte vs []rune
Entender a diferença entre esses três tipos é crucial para trabalhar com texto em Go:
texto := "Olá, Go! 🚀"
// String: sequência imutável de bytes
fmt.Println(len(texto)) // 16 bytes (não 10 caracteres!)
// []byte: sequência mutável de bytes
bytes := []byte(texto)
fmt.Println(len(bytes)) // 16
bytes[0] = 'o' // OK: modificável
// []rune: sequência de code points Unicode
runes := []rune(texto)
fmt.Println(len(runes)) // 10 (número real de caracteres)
runes[0] = 'o' // OK: modificável
| Tipo | Mutável | Unidade | Uso principal |
|---|---|---|---|
string | Não | bytes | Texto geral, chaves de map |
[]byte | Sim | bytes | I/O, manipulação binária, performance |
[]rune | Sim | code points | Manipulação de caracteres Unicode |
Concatenação de strings
Operador +
Simples, mas ineficiente para muitas concatenações:
resultado := "Hello" + ", " + "World" // OK para poucas strings
fmt.Sprintf
Flexível e legível para formatação:
nome := "Go"
versao := 1.22
msg := fmt.Sprintf("Linguagem: %s, Versão: %.2f", nome, versao)
strings.Builder — a forma eficiente
Para concatenação em loop, strings.Builder é a escolha correta:
import "strings"
func construirHTML(itens []string) string {
var b strings.Builder
b.WriteString("<ul>\n")
for _, item := range itens {
b.WriteString(" <li>")
b.WriteString(item)
b.WriteString("</li>\n")
}
b.WriteString("</ul>")
return b.String()
}
strings.Builder minimiza alocações ao crescer o buffer interno de forma eficiente. Se souber o tamanho aproximado, use b.Grow(n) para pré-alocar:
var b strings.Builder
b.Grow(1024) // pré-aloca 1KB
strings.Join
Para concatenar slices de strings com separador:
palavras := []string{"Go", "é", "incrível"}
frase := strings.Join(palavras, " ") // "Go é incrível"
Comparação de performance
// LENTO: O(n²) — cria nova string a cada iteração
func concatenarLento(itens []string) string {
resultado := ""
for _, item := range itens {
resultado += item // nova alocação a cada iteração!
}
return resultado
}
// RÁPIDO: O(n) — uma única alocação final
func concatenarRapido(itens []string) string {
var b strings.Builder
for _, item := range itens {
b.WriteString(item)
}
return b.String()
}
O pacote strings
O pacote strings da standard library oferece tudo para manipulação de strings:
import "strings"
s := " Golang Brasil "
// Busca
strings.Contains(s, "Brasil") // true
strings.HasPrefix(s, " Go") // true
strings.HasSuffix(s, "il ") // true
strings.Index(s, "Brasil") // 10
strings.Count("banana", "a") // 3
// Transformação
strings.ToUpper("go") // "GO"
strings.ToLower("GO") // "go"
strings.TrimSpace(s) // "Golang Brasil"
strings.Replace("aaa", "a", "b", 2) // "bba"
strings.ReplaceAll("aaa", "a", "b") // "bbb"
// Divisão e junção
strings.Split("a,b,c", ",") // ["a", "b", "c"]
strings.Fields(" go lang ") // ["go", "lang"]
strings.Join([]string{"a","b"}, "-") // "a-b"
// Repetição
strings.Repeat("Go! ", 3) // "Go! Go! Go! "
Unicode e UTF-8
Go trata strings como bytes UTF-8. Para trabalhar corretamente com caracteres internacionais:
Iteração por bytes vs runes
texto := "café"
// Iteração por BYTES — geralmente não é o que você quer
for i := 0; i < len(texto); i++ {
fmt.Printf("byte[%d] = %x\n", i, texto[i])
}
// byte[0]=63 byte[1]=61 byte[2]=66 byte[3]=c3 byte[4]=a9
// 'é' ocupa 2 bytes em UTF-8!
// Iteração por RUNES — forma correta para texto
for i, r := range texto {
fmt.Printf("rune[%d] = %c (U+%04X)\n", i, r, r)
}
// rune[0] = c, rune[1] = a, rune[2] = f, rune[3] = é
O range sobre strings automaticamente decodifica UTF-8 e entrega runes, enquanto indexação direta (s[i]) acessa bytes.
Contagem de caracteres
import "unicode/utf8"
texto := "Olá, 🌍!"
fmt.Println(len(texto)) // 12 bytes
fmt.Println(utf8.RuneCountInString(texto)) // 7 caracteres
Verificação de UTF-8
import "unicode/utf8"
fmt.Println(utf8.ValidString("Olá")) // true
fmt.Println(utf8.ValidString("\xff\xfe")) // false
Substrings
Substrings em Go compartilham a memória da string original:
original := "Golang Brasil"
sub := original[7:13] // "Brasil"
// sub aponta para os mesmos bytes em memória que original
// Isso é eficiente, mas mantenha cuidado com strings grandes
Para caracteres multi-byte, fatiar por bytes pode cortar runes no meio:
texto := "café"
// texto[0:4] = "caf\xc3" — CORTOU o 'é' no meio!
// Use []rune para fatiar por caracteres:
runes := []rune(texto)
sub := string(runes[0:4]) // "café" — correto
Conversões comuns
import "strconv"
// String para número
i, err := strconv.Atoi("42") // int
f, err := strconv.ParseFloat("3.14", 64) // float64
b, err := strconv.ParseBool("true") // bool
// Número para string
s := strconv.Itoa(42) // "42"
s = strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
s = fmt.Sprintf("%d", 42) // "42" (mais flexível)
// String <-> []byte (cópia)
bytes := []byte("hello")
str := string(bytes)
Para tratamento de erros na conversão, sempre verifique o valor err retornado pelas funções de parsing.
Strings em contextos práticos
JSON
import "encoding/json"
type Usuario struct {
Nome string `json:"nome"`
Email string `json:"email"`
}
u := Usuario{Nome: "Ana", Email: "ana@exemplo.com"}
dados, _ := json.Marshal(u)
fmt.Println(string(dados)) // {"nome":"Ana","email":"ana@exemplo.com"}
HTTP
// Lendo body como string
body, _ := io.ReadAll(resp.Body)
conteudo := string(body)
Esses padrões são fundamentais ao construir APIs REST e microsserviços.
Boas práticas com strings
- Use
strings.Builderpara concatenação em loops — nunca+=repetidamente - Use
rangepara iterar por caracteres — não indexação por bytes - Use raw strings para regex, SQL, JSON e paths
- Converta para
[]runequando precisar manipular caracteres Unicode - Use
utf8.RuneCountInStringpara contar caracteres, nãolen() - Prefira
strings.EqualFoldpara comparação case-insensitive - Pré-aloque
strings.BuildercomGrow()quando souber o tamanho
Para mais sobre tipos fundamentais, explore o glossário completo e o guia de Go para iniciantes.
Perguntas frequentes (FAQ)
Por que len() retorna bytes e não caracteres em strings Go?
Porque strings em Go são sequências de bytes, não de caracteres. Um caractere Unicode pode ocupar de 1 a 4 bytes em UTF-8. len("café") retorna 5 (não 4) porque ‘é’ ocupa 2 bytes. Use utf8.RuneCountInString() para contar caracteres reais, ou []rune() para converter a string em uma sequência de runes indexável por caractere.
Qual a forma mais eficiente de concatenar strings em Go?
Para poucas strings, o operador + é suficiente. Para concatenação em loop, use strings.Builder que minimiza alocações. Para juntar um slice com separador, use strings.Join. Nunca use += em loops — cada iteração cria uma nova string, resultando em complexidade O(n²) e muitas alocações desnecessárias.
Qual a diferença entre string e []byte em Go?
string é imutável e segura para uso concorrente entre goroutines sem sincronização. []byte é mutável e usada para I/O, manipulação binária e quando performance exige evitar cópias. A conversão entre elas ([]byte(s) e string(b)) geralmente envolve cópia dos dados, exceto em otimizações do compilador.
Como trabalhar com emojis e caracteres especiais em strings Go?
Emojis e caracteres especiais são runes que ocupam múltiplos bytes em UTF-8. Para manipulá-los corretamente: (1) use range para iterar (decodifica runes automaticamente); (2) converta para []rune antes de fatiar; (3) use unicode/utf8 para contagem e validação. Nunca fatie strings por índice de byte quando o texto pode conter caracteres multi-byte.