Por que a concatenação simples de strings em Go pode se tornar um problema?
\n\nTodo mundo que começou a estudar Go, mais cedo ou mais tarde, se depara com a tarefa: precisa juntar várias strings em uma. A maneira mais óbvia é usar o operador += ou a simples adição +. E isso funciona… até você tentar juntar mil strings. De repente, seu código começa a ficar lento e a memória é gasta como na Black Friday.
Por que isso acontece? Em Go, as strings são imutáveis (immutable). Quando você escreve s += "new", o Go não adiciona o texto ao final da string antiga. Ele cria uma nova string, copiando o conteúdo de ambas as partes. Em cada operação, nova memória é alocada. Se houver milhares dessas operações, o coletor de lixo começa a trabalhar em modo de emergência e a performance cai.
Neste artigo, vamos analisar todas as formas de concatenação de strings em Go: desde as ingênuas até as industriais. Você aprenderá quando usar cada método e poderá escrever código que não seja vergonha mostrar em uma code review.
\n\nFormas de concatenação de strings em Go: do simples ao eficiente
\n\nVamos considerar cinco abordagens principais. Para cada uma, darei um exemplo de código e explicarei em quais situações ela é adequada.
\n\n1. Operador + e += (forma ingênua)
\n\nO método mais simples e intuitivo. Ideal para juntar 2 a 5 strings.
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n hello := "Hello"\n world := "World"\n result := hello + ", " + world + "!"\n fmt.Println(result) // Hello, World!\n}\n\n\nQuando usar: apenas para um pequeno número de strings. Se você estiver escrevendo um loop de 1000 iterações com += — pare. Isso é complexidade quadrática de memória (O(n²)).
2. fmt.Sprintf
\n\nÚtil quando você precisa formatar a saída. Internamente, ele também cria novas strings, mas faz isso de forma mais eficiente do que a junção manual.
\n\nname := "Alice"\nage := 30\nresult := fmt.Sprintf("My name is %s and I am %d years old.", name, age)\nfmt.Println(result)\n\n\nQuando usar: quando você precisa não apenas juntar, mas formatar uma string com diferentes tipos de dados. Para concatenação simples, não é a melhor escolha devido ao custo adicional de analisar o template.
\n\n3. strings.Join — o rei para slices de strings
\n\nSe você já tem um array ou slice de strings, strings.Join é a forma mais rápida e elegante.
package main\n\nimport (\n "fmt"\n "strings"\n)\n\nfunc main() {\n words := []string{"Go", "is", "awesome", "for", "backend"}\n result := strings.Join(words, " ")\n fmt.Println(result) // Go is awesome for backend\n}\n\n\nInternamente, strings.Join primeiro calcula o comprimento total do resultado, aloca memória uma vez e a preenche. Sem cópias desnecessárias.
Quando usar: sempre que você tiver um slice de strings pronto. É a opção ideal para construir consultas SQL, strings CSV e URLs.
\n\n4. bytes.Buffer — o clássico do gênero
\n\nO pacote bytes fornece um buffer que cresce dinamicamente. Ele implementa a interface io.Writer, então você pode escrever nele através de WriteString.
package main\n\nimport (\n "bytes"\n "fmt"\n)\n\nfunc main() {\n var buffer bytes.Buffer\n\n for i := 0; i < 5; i++ {\n buffer.WriteString("Line ")\n buffer.WriteString(fmt.Sprintf("%d\\n", i))\n }\n\n fmt.Println(buffer.String())\n}\n\n\nO buffer aloca memória em blocos (geralmente de 64 bytes), o que reduz significativamente o número de alocações em comparação com +=.
Quando usar: em loops onde a quantidade de strings não é conhecida antecipadamente. Por exemplo, ao gerar HTML ou logs.
\n\n5. strings.Builder — o padrão moderno (Go 1.10+)
\n\nA partir do Go 1.10, surgiu um