Go

Dominando Make e New em Go: Diferenças Práticas na Alocação de Memória em Projetos Reais

10 min de leitura

Dominando Make e New em Go: Diferenças Práticas na Alocação de Memória em Projetos Reais

Make e New em Go: Diferenças Práticas na Alocação de Memória Quando você começa a trabalhar com Go, uma das primeiras confusões que surge é entender quando usar e quando usar . À 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. 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: aloca memória e retorna um ponteiro zerado, enquanto 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. Entendendo o : Alocação Simples O é a função mais simples de alocação em Go. Ele recebe um tipo como argumento, aloca memória suficiente para armazenar

<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 &quot;fmt&quot;

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: &quot;&quot; (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 &quot;fmt&quot;

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 &quot;fmt&quot;

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[&quot;age&quot;] = 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 (

&quot;fmt&quot;

&quot;time&quot;

)

func main() {

// Sem pre-alocação (ineficiente)

start := time.Now()

slice1 := make([]int, 0)

for i := 0; i &lt; 1000000; i++ {

slice1 = append(slice1, i)

}

fmt.Printf(&quot;Sem pre-alocação: %v\n&quot;, time.Since(start))

// Com pre-alocação (eficiente)

start = time.Now()

slice2 := make([]int, 0, 1000000)

for i := 0; i &lt; 1000000; i++ {

slice2 = append(slice2, i)

}

fmt.Printf(&quot;Com pre-alocação: %v\n&quot;, 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 &quot;fmt&quot;

func main() {

// Map com capacidade inicial

myMap := make(map[string]int, 100)

// Operações posteriores serão mais eficientes

for i := 1; i &lt;= 100; i++ {

myMap[fmt.Sprintf(&quot;key%d&quot;, 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 &quot;fmt&quot;

func processData(data map[string]int) {

// Se alguém passar nil aqui, pânico!

data[&quot;count&quot;] = 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[&quot;count&quot;] = 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 &quot;fmt&quot;

type Config struct {

Settings map[string]string

Values []int

}

func main() {

// ERRADO: campos map e slice ficarão nil

conf1 := new(Config)

// conf1.Settings[&quot;key&quot;] = &quot;value&quot; // pânico

// CORRETO: usando composição com make

conf2 := &amp;Config{

Settings: make(map[string]string),

Values: make([]int, 0),

}

conf2.Settings[&quot;key&quot;] = &quot;value&quot;

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 &quot;fmt&quot;

type Database struct {

cache map[string]interface{}

logs []string

connected bool

}

// Construtor adequado

func NewDatabase() *Database {

return &amp;Database{

cache: make(map[string]interface{}),

logs: make([]string, 0),

connected: false,

}

}

func main() {

db := NewDatabase()

db.cache[&quot;user&quot;] = &quot;João&quot;

db.logs = append(db.logs, &quot;Database inicializado&quot;)

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 &quot;fmt&quot;

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) // &amp;[10 0 0 0 0]

// new com slice: problema

slicePtr := new([]int)

fmt.Println(slicePtr) // &amp;[]

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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Guia Completo de gRPC em Go: Protocol Buffers, Streaming e Interceptors
Guia Completo de gRPC em Go: Protocol Buffers, Streaming e Interceptors

O que é gRPC e Por Que Importa gRPC é um framework de chamada de procedimento...

Guia Completo de Context em Go: Cancelamento, Timeout e Propagação de Valores
Guia Completo de Context em Go: Cancelamento, Timeout e Propagação de Valores

Context em Go: Cancelamento, Timeout e Propagação de Valores O é um dos pacot...

O que Todo Dev Deve Saber sobre Pacote os e filepath em Go: Sistema de Arquivos e Ambiente
O que Todo Dev Deve Saber sobre Pacote os e filepath em Go: Sistema de Arquivos e Ambiente

Entendendo o Pacote em Go O pacote é fundamental para qualquer programa Go qu...