Pular para o conteúdo

Trilha de Go · parte 10 de 65

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

Tamanho da fonte:
Largura da coluna:
Tom:
Espaço entre linhas:
Fonte:

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

ParteSeçõesO que você vai aprender
1. Fundamentos e intuição1 a 3O conceito simples, o valor zero e a evolução histórica até o Go 1.26 com expressões
2. Mecânica e memória4 a 6O que acontece na RAM, new() versus make() e o mito da alocação forçada no Heap
3. Comparativos e idiomas7 a 9new(T) versus literal &T{}, ponteiros para tipos primitivos e campos opcionais em APIs
4. Armadilhas e erros comuns10 a 12O perigo de usar new() em slices, maps e canais, e o falso senso de inicialização
5. Prática e consolidação13 a 16Trê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 evolução do comando new: Do Go 1.0 ao Go 1.26. new(int) -> *int (Valor 0) → Variável Auxiliar Obrigatória (exige variável temporária ou helper Ptr(v))…
figura 1 · A evolução do comando new: Do Go 1.0 ao Go 1.26

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 int ou float64, 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:

  1. 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).
  2. Retorno:
    • new() sempre devolve um ponteiro (*T).
    • make() sempre devolve o tipo concreto direto (T), e nunca um ponteiro.
  3. 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 com len e cap, ou cria os buffers e filas de bloqueio de um canal.
Comparativo definitivo: new versus make na memória. new(Usuario) → Memória Zerada (reserva bloco contíguo na stack/heap)…
figura 2 · Comparativo definitivo: new versus make na memória

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!
}
Escape analysis com a função new: Stack versus Heap. Chamada new(T) → Ponteiro escapa do escopo? (compilador avalia ciclo de vida)…
figura 3 · Escape analysis com a função new: Stack versus Heap

A figura ilustra essa árvore de decisão do compilador:

  • Em SomarLocal, o compilador constata que o ponteiro p tem 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ção runtime.newobject e 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 *[]int apontando para um slice nulo (sem array subjacente).
  • new(chan int) devolve um *chan int apontando 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 (como struct{}) 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

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?