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
TipoMutávelUnidadeUso principal
stringNãobytesTexto geral, chaves de map
[]byteSimbytesI/O, manipulação binária, performance
[]runeSimcode pointsManipulaçã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

  1. Use strings.Builder para concatenação em loops — nunca += repetidamente
  2. Use range para iterar por caracteres — não indexação por bytes
  3. Use raw strings para regex, SQL, JSON e paths
  4. Converta para []rune quando precisar manipular caracteres Unicode
  5. Use utf8.RuneCountInString para contar caracteres, não len()
  6. Prefira strings.EqualFold para comparação case-insensitive
  7. Pré-aloque strings.Builder com Grow() 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.