<h2>Make e New em Go: Diferenças Práticas na Alocação de Memória</h2>
<p>Quando você começa a trabalhar com Go, uma das primeiras confusões que surge é entender quando usar <code>make</code> e quando usar <code>new</code>. À primeira vista, ambos criam espaço na memória, mas servem propósitos completamente diferentes. Neste artigo, vamos desvendar essas diferenças de forma prática, mostrando não apenas o que cada um faz, mas por que você precisa escolher corretamente para evitar bugs silenciosos em produção.</p>
<p>A confusão é natural porque outras linguagens como C e C++ usam uma abordagem unificada para alocação. Go, no entanto, foi projetado com filosofia diferente: <code>new</code> aloca memória e retorna um ponteiro zerado, enquanto <code>make</code> aloca, inicializa e retorna um valor já pronto para uso. Essa distinção existe porque Go trabalha com tipos que precisam de inicialização antes de serem úteis.</p>
<h2>Entendendo o <code>new</code>: Alocação Simples</h2>
<p>O <code>new</code> é a função mais simples de alocação em Go. Ele recebe um tipo como argumento, aloca memória suficiente para armazenar um valor desse tipo, zera essa memória (preenche com os valores padrão) e retorna um ponteiro para esse espaço alocado.</p>
<p>O aspecto crítico aqui é que <code>new</code> <strong>apenas aloca e zera</strong> — não inicializa estruturas internas que alguns tipos exigem. Vamos a um exemplo prático:</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// new com um inteiro
ptr := new(int)
fmt.Println(*ptr) // Imprime: 0
// new com uma string
strPtr := new(string)
fmt.Println(*strPtr) // Imprime: "" (string vazia)
// new com um slice
slicePtr := new([]int)
fmt.Println(*slicePtr) // Imprime: [] (slice nil)
fmt.Println(len(*slicePtr)) // Imprime: 0
}</code></pre>
<p>Perceba que <code>new</code> retorna <strong>sempre um ponteiro</strong>. Se você tentar usar diretamente sem desreferenciar, não consegue acessar o valor. Além disso, para tipos como slices, maps e channels, usar <code>new</code> deixa-os em estado <code>nil</code>, o que causa pânicos se você tentar operá-los:</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// Tentando usar new com slice
slicePtr := new([]int)
// slicePtr é um ponteiro para um slice nil
// Isto causaria pânico:
// slicePtr = append(slicePtr, 1) // panic: assignment to entry in nil map
// Isto funciona, mas é incômodo:
*slicePtr = make([]int, 0)
slicePtr = append(slicePtr, 1)
fmt.Println(*slicePtr) // [1]
}</code></pre>
<p>Use <code>new</code> quando você precisa de um ponteiro para um tipo simples (struct, array de tamanho fixo) e quer que seus campos sejam inicializados com valores zero. Não use para slices, maps ou channels.</p>
<h2>Make: Alocação Inteligente com Inicialização</h2>
<p>O <code>make</code> é mais sofisticado. Funciona apenas com três tipos: <strong>slices</strong>, <strong>maps</strong> e <strong>channels</strong>. Diferentemente de <code>new</code>, <code>make</code> retorna <strong>um valor do tipo em si</strong> (não um ponteiro) e garante que a estrutura interna esteja completamente inicializada e pronta para uso.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// make com slice
slice := make([]int, 5)
fmt.Println(slice) // [0 0 0 0 0]
fmt.Println(len(slice)) // 5
fmt.Println(cap(slice)) // 5
// make com map
myMap := make(map[string]int)
myMap["age"] = 30
fmt.Println(myMap) // map[age:30]
// make com channel
ch := make(chan int)
// ch agora está pronto para enviar/receber
}</code></pre>
<p>Note as diferenças críticas: <code>make</code> retorna um slice, não um ponteiro para um slice. Você pode usá-lo imediatamente. Para slices, você pode especificar capacidade inicial, o que é crucial para performance em loops:</p>
<pre><code class="language-go">package main
import (
"fmt"
"time"
)
func main() {
// Sem pre-alocação (ineficiente)
start := time.Now()
slice1 := make([]int, 0)
for i := 0; i < 1000000; i++ {
slice1 = append(slice1, i)
}
fmt.Printf("Sem pre-alocação: %v\n", time.Since(start))
// Com pre-alocação (eficiente)
start = time.Now()
slice2 := make([]int, 0, 1000000)
for i := 0; i < 1000000; i++ {
slice2 = append(slice2, i)
}
fmt.Printf("Com pre-alocação: %v\n", time.Since(start))
}</code></pre>
<p>A pré-alocação com <code>make</code> economiza realocações desnecessárias. Maps também aceitam capacidade inicial:</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// Map com capacidade inicial
myMap := make(map[string]int, 100)
// Operações posteriores serão mais eficientes
for i := 1; i <= 100; i++ {
myMap[fmt.Sprintf("key%d", i)] = i
}
fmt.Println(len(myMap)) // 100
}</code></pre>
<h2>Aplicações Práticas e Armadilhas Comuns</h2>
<p>Agora que você entende a teoria, vamos ver onde essas funções realmente se diferenciam em código do mundo real. A principal armadilha é tentar usar <code>new</code> com tipos que exigem <code>make</code>:</p>
<pre><code class="language-go">package main
import "fmt"
func processData(data map[string]int) {
// Se alguém passar nil aqui, pânico!
data["count"] = 1 // panic: assignment to entry in nil map
}
func main() {
// ERRADO: isso cria um ponteiro para um map nil
wrongMap := new(map[string]int)
// CORRETO: isso cria um map inicializado
correctMap := make(map[string]int)
correctMap["count"] = 1
processData(correctMap)
}</code></pre>
<p>Outra situação real: ao trabalhar com structs que contêm slices ou maps:</p>
<pre><code class="language-go">package main
import "fmt"
type Config struct {
Settings map[string]string
Values []int
}
func main() {
// ERRADO: campos map e slice ficarão nil
conf1 := new(Config)
// conf1.Settings["key"] = "value" // pânico
// CORRETO: usando composição com make
conf2 := &Config{
Settings: make(map[string]string),
Values: make([]int, 0),
}
conf2.Settings["key"] = "value"
conf2.Values = append(conf2.Values, 1)
fmt.Println(conf2)
}</code></pre>
<p>Uma boas prática em Go é sempre fornecer funções construtoras (factory functions) para suas structs quando elas contêm tipos que precisam de inicialização:</p>
<pre><code class="language-go">package main
import "fmt"
type Database struct {
cache map[string]interface{}
logs []string
connected bool
}
// Construtor adequado
func NewDatabase() *Database {
return &Database{
cache: make(map[string]interface{}),
logs: make([]string, 0),
connected: false,
}
}
func main() {
db := NewDatabase()
db.cache["user"] = "João"
db.logs = append(db.logs, "Database inicializado")
fmt.Println(db.cache)
fmt.Println(db.logs)
}</code></pre>
<h2>Quadro Comparativo e Guia de Decisão</h2>
<p>Para consolidar o aprendizado, aqui está quando usar cada um:</p>
<p><strong>Use <code>new</code> quando:</strong></p>
<ul>
<li>Você precisa de um ponteiro para um tipo simples (int, string, struct)</li>
<li>Os campos dessa struct não contêm slices, maps ou channels não inicializados</li>
<li>Você quer que tudo seja zerado e não precisa de operações subsequentes</li>
</ul>
<p><strong>Use <code>make</code> quando:</strong></p>
<ul>
<li>Você está trabalhando com slices, maps ou channels</li>
<li>Precisa especificar tamanho inicial ou capacidade</li>
<li>Quer um valor pronto para uso imediato</li>
</ul>
<p>Um exemplo final que demonstra a diferença comportamental:</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// new com array de tamanho fixo: funciona bem
arr := new([5]int)
arr[0] = 10 // Funciona porque array de tamanho fixo é como uma struct
fmt.Println(arr) // &[10 0 0 0 0]
// new com slice: problema
slicePtr := new([]int)
fmt.Println(slicePtr) // &[]
fmt.Println(*slicePtr) // []
fmt.Println(len(*slicePtr)) // 0
// Mesmo tentando usar, é problemático
// slicePtr = append(slicePtr, 1) // Isto funciona, mas é confuso
// make com slice: natural
slice := make([]int, 5)
slice[0] = 10
fmt.Println(slice) // [10 0 0 0 0]
slice = append(slice, 20)
fmt.Println(slice) // [10 0 0 0 0 20]
}</code></pre>
<h2>Conclusão</h2>
<p>Aprendemos que <code>new</code> e <code>make</code> servem propósitos distintos e não são intercambiáveis. <code>new</code> aloca memória e retorna um ponteiro zerado — perfeito para tipos simples. <code>make</code> aloca, inicializa e retorna um valor pronto para uso — essencial para slices, maps e channels. A escolha errada causa bugs que podem ser silenciosos em desenvolvimento e explodem em produção. Pratique reconhecendo qual função usar analisando o tipo que você está alocando: se é slice, map ou channel, <code>make</code> é sua resposta; caso contrário, <code>new</code> geralmente é mais apropriado para ponteiros. Por fim, construa hábitos de escrever funções construtoras (factory functions) em suas structs, especialmente quando elas contêm tipos que precisam de inicialização — isso torna seu código mais seguro e expressivo.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://golang.org/ref/spec#Built-in_functions" target="_blank" rel="noopener noreferrer">Go Language Specification - Built-in functions</a></li>
<li><a href="https://golang.org/doc/effective_go#allocation_new" target="_blank" rel="noopener noreferrer">Effective Go - Allocation with new</a></li>
<li><a href="https://golang.org/doc/effective_go#allocation_make" target="_blank" rel="noopener noreferrer">Effective Go - Allocation with make</a></li>
<li><a href="https://golang.org/ref/mem" target="_blank" rel="noopener noreferrer">Go Memory Model</a></li>
<li><a href="https://blog.golang.org/maps" target="_blank" rel="noopener noreferrer">The Go Blog - Maps</a></li>
</ul>
<p><!-- FIM --></p>