Função new em Go
O que a função embutida new() faz em Go: alocação com valor zero, stack ou heap, diferença para make e &T{} e a novidade do Go 1.26.
intermediárioPublicada em · 12 min de leitura
A função embutida new() em Go é uma primitiva de alocação de memória que reserva espaço para um determinado tipo, inicializa todos os seus bytes com o valor zero e devolve um ponteiro (*T) apontando para esse espaço recém-criado.
Historicamente, do Go 1.0 até o Go 1.25, a função new() aceitava exclusivamente um tipo como parâmetro (new(T)), devolvendo sempre um ponteiro para o valor zero daquele tipo. A partir do Go 1.26, a linguagem passou a permitir que new() receba diretamente expressões de valor (new(42), new("texto")), eliminando anos de código auxiliar e funções utilitárias que os desenvolvedores eram forçados a criar para instanciar ponteiros de tipos primitivos.
Esta aula ensina o funcionamento completo de new(), sua evolução histórica, o comportamento da alocação entre Stack e Heap via , a diferença vital em relação ao make() e ao literal &T{}, e as armadilhas clássicas ao lidar com estruturas de dados em Go.
Mapa da aula
| Parte | Seções | O que você vai aprender |
|---|---|---|
| 1. Fundamentos e intuição | 1 a 3 | O conceito simples, o valor zero e a evolução histórica até o Go 1.26 com expressões |
| 2. Mecânica e memória | 4 a 6 | O que acontece na RAM, new() versus make() e o mito da alocação forçada no Heap |
| 3. Comparativos e idiomas | 7 a 9 | new(T) versus literal &T{}, ponteiros para tipos primitivos e campos opcionais em APIs |
| 4. Armadilhas e erros comuns | 10 a 12 | O perigo de usar new() em slices, maps e canais, e o falso senso de inicialização |
| 5. Prática e consolidação | 13 a 16 | Três exercícios práticos graduados com gabarito, glossário técnico, fontes oficiais e leituras relacionadas |
Pré-requisitos recomendados: conceitos básicos de Go, Ponteiros e Structs.
Parte 1: Fundamentos e intuição
1. O que é a função new()
Imagine que você gerencie um depósito de gavetas numeradas. Quando um novo cliente chega e pede um espaço para guardar objetos, você não constrói um armário novo no quintal: você simplesmente aloca uma gaveta vazia, varre qualquer poeira que estivesse lá dentro para deixá-la 100% limpa (zerada) e entrega na mão do cliente uma chave etiquetada com o número daquela gaveta. A chave não é a gaveta: ela é o ponteiro que dá acesso àquele espaço reservado.
A função embutida new() no Go faz exatamente isso. Ela é uma função nativa da linguagem (portanto, você não precisa importar nenhum pacote para usá-la) que reserva o espaço em bytes exigido por um tipo, zera todos os seus bits na memória e entrega de volta o endereço físico (um ponteiro) daquela gaveta limpa.
A sintaxe clássica estabelecida desde o lançamento da linguagem é direta:
package main
import "fmt"
func main() {
// Aloca memória para um inteiro zerado e devolve *int
p := new(int)
fmt.Printf("Endereço alocado por new: %p\n", p)
fmt.Printf("Conteúdo padrão zerado (*p): %d\n", *p) // Imprime 0
*p = 100
fmt.Printf("Novo valor gravado (*p): %d\n", *p) // Imprime 100
}
O tipo retornado por new(T) é invariavelmente *T. Se você chamar new(string), o retorno será um *string apontando para uma string vazia "". Se chamar new(bool), recebe um *bool apontando para false. Se chamar new(Usuario), recebe um *Usuario com todos os campos da struct zerados.
2. A evolução no Go 1.26: new() com expressões de valor
Por mais de quinze anos (do Go 1.0 ao Go 1.25), o operando da função new podia ser única e exclusivamente o identificador de um tipo. Se você quisesse criar um ponteiro para um número inteiro que já nascesse com o valor 42, a linguagem impedia você de escrever new(42), pois 42 é um valor literal, e não um tipo.
Essa restrição causava um desconforto imenso no desenvolvimento de APIs modernas, especialmente ao trabalhar com JSON, bancos de dados SQL com campos nulos e SDKs de provedores de nuvem (como AWS e GCP), onde campos opcionais de structs são modelados como ponteiros (*string, *int, *bool):
// Cenário típico antes do Go 1.26:
type AtualizarUsuarioDTO struct {
Nome *string `json:"nome,omitempty"`
Idade *int `json:"idade,omitempty"`
}
// Para instanciar isso, o desenvolvedor era forçado a fazer:
nome := "Carlos"
idade := 30
dto := AtualizarUsuarioDTO{
Nome: &nome, // Exigia variável intermediária!
Idade: &idade,
}
Para contornar esse incômodo, quase todo projeto Go mantinha um arquivo de utilitários com uma função genérica auxiliar chamada Ptr:
func Ptr[T any](v T) *T {
return &v
}
A partir do Go 1.26, a especificação da linguagem deu um salto de ergonomia aguardado há anos: a função new() foi estendida para aceitar expressões de valor como argumento!
A figura ilustra essa evolução. No modelo anterior, o programador precisava alocar variáveis temporárias na stack para conseguir extrair o endereço com &. No Go 1.26 em diante, a instanciação é direta, concisa e idiomática:
// Go 1.26+: Sintaxe moderna e nativa
dto := AtualizarUsuarioDTO{
Nome: new("Carlos"), // Aloca e inicializa diretamente com *string
Idade: new(30), // Aloca e inicializa diretamente com *int
}
// Aceita expressões computadas de qualquer tipo
taxaPtr := new(float64(base * 1.15))
O compilador avalia a expressão fornecida, infere o seu tipo estático, reserva o bloco de memória adequado, copia o valor resultante para dentro dele e devolve o ponteiro pronto em um único passo sem boilerplate.
Parte 2: Mecânica e memória
3. O que acontece fisicamente na RAM
Quando a instrução new() é invocada, o compilador consulta a tabela de tipos para saber quantos bytes aquele tipo requer na arquitetura do sistema:
- Para tipos primitivos como
intoufloat64, reserva 8 bytes (em 64 bits). - Para structs, calcula a soma do tamanho dos campos mais o padding de alinhamento de memória (estudado na aula de Structs).
- Preenche todos os bytes alocados com
0x00(zeramento de memória).
Um detalhe de vital importância: se o tipo solicitado tiver tamanho zero (como a struct vazia struct{} estudada anteriormente), o Go não aloca nenhum byte no gerenciador de memória. A chamada new(struct{}) simplesmente devolve um ponteiro apontando para o endereço global estático runtime.zerobase.
4. new() versus make(): o comparativo definitivo
Uma das dúvidas mais recorrentes de quem aprende Go é a diferença entre new() e make(). Ambas são funções embutidas de alocação, mas operam em universos completamente distintos da arquitetura da linguagem.
As diferenças fundamentais são:
- Abrangência de tipos:
new()é universal: aceita qualquer tipo concebível em Go (primitivos, structs, arrays, interfaces).make()é extremamente restrito: aceita única e exclusivamente três tipos internos da linguagem: slices ([]T), maps (map[K]V) e channels (chan T).
- Retorno:
new()sempre devolve um ponteiro (*T).make()sempre devolve o tipo concreto direto (T), e nunca um ponteiro.
- Nível de inicialização:
new()apenas zera os bytes na memória. Ele não inicializa estruturas internas.make()inicializa as estruturas complexas do runtime: aloca a tabela de buckets de um map, configura o array subjacente de um slice comlenecap, ou cria os buffers e filas de bloqueio de um canal.
O diagrama acima coloca lado a lado os dois comportamentos. O new(Usuario) entrega um bloco plano de memória zerada com um ponteiro apontando para ele. Já o make([]int, 0, 10) configura nos bastidores o cabeçalho de 24 bytes com o ponteiro para o array real, o tamanho inicial zero e a capacidade dez, entregando uma estrutura pronta para receber append().
5. O mito da alocação forçada no Heap
Existe um mito muito antigo, herdado de linguagens como C++ e Java (onde a palavra-chave new é sinônimo obrigatório de alocação dinâmica no Heap), de que chamar new() em Go forçaria a memória a ir para o Heap e geraria trabalho extra para o Garbage Collector.
Em Go, isso é categoricamente falso. A palavra new() não tem nenhuma relação com o local físico onde a memória será instalada.
Quem decide soberanamente se a alocação ocorre na Stack ou no Heap é o algoritmo de Escape Analysis (Análise de Fuga) do compilador:
package main
// Caso 1: new() alocado na STACK (custo zero!)
func SomarLocal() int {
p := new(int) // O ponteiro p nunca sai de dentro desta função
*p = 10 + 20
return *p // Retorna o valor primitivo copiado, não o ponteiro!
}
// Caso 2: new() alocado no HEAP (gerenciado pelo GC)
func ObterPonteiro() *int {
p := new(int) // O ponteiro é retornado para fora do escopo
*p = 42
return p // Escapa para a heap!
}
A figura ilustra essa árvore de decisão do compilador:
- Em
SomarLocal, o compilador constata que o ponteiroptem ciclo de vida estritamente restrito à função. Ele aloca o espaço diretamente no frame da pilha (Stack). A desalocação ocorre instantaneamente no retorno da função com custo computacional nulo, sem incomodar o coletor de lixo. - Em
ObterPonteiro, o ponteiro sobrevive ao término do frame. A Escape Analysis emite uma instruçãoruntime.newobjecte aloca o bloco no Heap compartilhado.
Você pode confirmar isso na prática passando a flag de diagnóstico: go build -gcflags="-m" main.go. O compilador emitirá new(int) does not escape para a primeira função e new(int) escapes to heap para a segunda.
Pergunta de entrevistaQual a diferença entre new e make?
new(T) aloca um T zerado e devolve *T. make só serve para slice, map e channel, e inicializa a estrutura interna, então o valor já pode ser usado. Um new de slice devolve ponteiro para um slice nil, e um new de map devolve um map nil, que não aceita escrita. Por isso uso make para esses tipos, e &T{} quando quero inicializar campos.
Parte 3: Comparativos e idiomas
6. new(T) versus a inicialização literal &T{}
Para structs, o Go oferece duas formas sintáticas de obter um ponteiro:
type Servidor struct {
Host string
Porta int
}
// Forma 1: usando new
s1 := new(Servidor)
s1.Host = "localhost"
s1.Porta = 8080
// Forma 2: usando literal com operador de endereço &
s2 := &Servidor{
Host: "localhost",
Porta: 8080,
}
Qual das duas é a mais idiomática?
A comunidade Go e os criadores da linguagem recomendam amplamente a Forma 2 (&T{...}) para inicialização de structs. A sintaxe de literal composto permite preencher os campos nomeados no mesmo instante da alocação, tornando o código legível, atômico e evitando o risco de alguém esquecer de configurar um campo obrigatório entre a criação com new e as linhas seguintes.
O uso clássico de new() permaneceu durante anos mais comum para alocar tipos primitivos zerados (new(int), new(sync.Mutex)) ou quando a intenção explícita do código é demonstrar que todos os campos devem permanecer deliberadamente com o valor zero útil. Agora, a partir do Go 1.26, o new() recuperou enorme protagonismo com sua nova capacidade de instanciar literais primitivos em expressões curtas (new(100)).
Parte 4: Armadilhas e erros comuns
7. O desastre de usar new() em slices, maps e canais
O erro conceitual mais perigoso envolvendo new() é utilizá-lo na tentativa de inicializar coleções ou canais. Analise o código abaixo:
package main
import "fmt"
func main() {
// ERRO GRAVE: new aloca e zera a memória
m := new(map[string]int)
fmt.Printf("Tipo de m: %T\n", m) // Imprime: *map[string]int
fmt.Printf("Valor de *m: %v\n", *m) // Imprime: map[] (nil map!)
// Tentativa de escrita em map nulo: PÂNICO IMEDIATO!
(*m)["chave"] = 10 // panic: assignment to entry in nil map
}
O que aconteceu aqui? O new(map[string]int) alocou espaço para um cabeçalho de mapa e o preencheu com zeros. O valor zero de um map em Go é nil. Portanto, m é um ponteiro para um map nulo! Mapas nulos podem ser lidos sem erro, mas qualquer tentativa de escrita em um map nil dispara um pânico fatal no runtime.
O mesmo comportamento incorreto ocorre com slices e canais:
new([]int)devolve um*[]intapontando para um slice nulo (sem array subjacente).new(chan int)devolve um*chan intapontando para um canal nulo (nil chan). Tentar enviar ou receber dados de um canal nulo bloqueia a goroutine para sempre em deadlock!
Para slices, maps e canais, use obrigatoriamente make() (ou o literal vazio map[string]int{}). Jamais utilize new() para esses três tipos a menos que você esteja construindo uma ferramenta muito específica de reflexão ou desserialização de baixo nível.
Parte 5: Prática e consolidação
8. Exercícios práticos graduados
Exercício 1: Instanciação moderna em Go 1.26+
Objetivo: Crie uma struct Configuracao com os campos opcionais Porta *int, Debug *bool e Ambiente *string. Instancie uma variável dessa struct utilizando a sintaxe moderna do Go 1.26 com new() para fornecer os valores iniciais 8080, true e "producao" sem criar variáveis intermediárias.
Solução recomendada:
package main
import "fmt"
type Configuracao struct {
Porta *int
Debug *bool
Ambiente *string
}
func main() {
// Utilizando o recurso do Go 1.26+: new() com expressões literais
cfg := Configuracao{
Porta: new(8080),
Debug: new(true),
Ambiente: new("producao"),
}
fmt.Printf("Configuração carregada: Porta %d, Debug %t, Ambiente %s\n",
*cfg.Porta, *cfg.Debug, *cfg.Ambiente)
}
Exercício 2: Inspeção de Escape Analysis com new()
Objetivo: Escreva um programa com duas funções que utilizem new([1000]int). Uma deve repassar o ponteiro para fora e a outra deve apenas somar os elementos internamente e devolver o total como valor. Compile usando go build -gcflags="-m" e verifique a saída do compilador.
Solução recomendada:
package main
// Fica na Stack: o compilador detecta que o array não vaza
func SomarLocal() int {
arr := new([1000]int)
total := 0
for _, v := range arr {
total += v
}
return total
}
// Escapa para a Heap: o ponteiro do array é devolvido
func CriarArrayHeap() *[1000]int {
arr := new([1000]int)
return arr // &arr escapes to heap
}
func main() {
_ = SomarLocal()
_ = CriarArrayHeap()
}
Exercício 3: Identificando e corrigindo inicialização nula
Objetivo: Explique por que o código abaixo falha e reescreva-o de forma idiomática utilizando a função correta:
// CÓDIGO COM FALHA
package main
func main() {
fila := new(chan int)
*fila <- 42 // O que acontece aqui?
}
Correção e explicação:
O código tenta enviar dados para um ponteiro que aponta para um canal nulo (nil), resultando em travamento perpétuo da goroutine (deadlock). A correção exige o uso de make() com a capacidade de buffer desejada:
package main
import "fmt"
func main() {
// Correto: make inicializa o canal operacionalmente
fila := make(chan int, 1)
fila <- 42
fmt.Printf("Valor recebido do canal: %d\n", <-fila)
}
9. Glossário técnico
new(): função embutida que aloca memória zerada para um tipo ou inicializa uma expressão (Go 1.26+), devolvendo um ponteiro*T.make(): função embutida exclusiva para inicializar estruturas complexas de slices, maps e channels, devolvendo o tipo concreto funcional.- Escape Analysis: análise em tempo de compilação que decide se um bloco alocado por
new()ou por literais deve residir na Stack (custo zero) ou no Heap (gerenciado pelo GC). runtime.zerobase: endereço global estático no runtime para onde todos os ponteiros de tipos de zero bytes (comostruct{}) apontam.- Nil Assignment: erro de pânico disparado ao tentar gravar chaves em um mapa alocado com
new(map[K]V)sem inicialização de buckets.
10. Fontes e leituras recomendadas
- Especificação Oficial da Linguagem Go (The Go Programming Language Specification), seção Allocation with new, atualizada para Go 1.26 (fevereiro de 2026).
- DONOVAN, Alan; KERNIGHAN, Brian. The Go Programming Language. Addison-Wesley, 2015. Capítulo 2.3.4: The new Function.
- Notas de Lançamento Oficiais do Go 1.26 (
golang.org/doc/go1.26), consultadas em outubro de 2026.
11. Relacionados
- Go
- Ponteiros
- Structs
- Heap
- Call Stack
- Garbage Collector
- Anatomia de Slices, Arrays e Maps