O que é New em Go?
A função new é uma função built-in de Go que aloca memória para um valor de qualquer tipo e retorna um ponteiro para essa memória, inicializada com o valor zero (zero value) do tipo. É a forma mais simples de obter um ponteiro para um valor recém-alocado sem precisar declarar uma variável intermediária.
A assinatura de new é extremamente simples: func new(Type) *Type. Ela aceita um tipo como argumento e retorna um ponteiro para uma nova instância desse tipo com todos os campos zerados. Não há construtores em Go — a filosofia da linguagem é que o zero value de qualquer tipo deve ser útil e seguro para uso imediato.
Na prática do dia a dia, new é relativamente pouco usado no código Go idiomático. A maioria dos desenvolvedores prefere a sintaxe &T{} (endereço de literal composto) que permite inicializar campos ao mesmo tempo. Porém, entender new é fundamental para compreender o modelo de memória de Go e como ele difere de make, que é usado para tipos de referência.
Sintaxe e comportamento básico
// new retorna *T, onde T é zerado
p := new(int)
fmt.Println(*p) // 0 (zero value de int)
fmt.Println(p) // 0xc0000b4008 (endereço de memória)
*p = 42
fmt.Println(*p) // 42
O valor retornado por new é um ponteiro válido — a memória já está alocada e pronta para uso. Isso contrasta com um ponteiro nil, que causaria panic ao ser desreferenciado:
// new: ponteiro válido para valor zero
var p1 *int = new(int) // p1 aponta para 0
*p1 = 10 // OK
// Sem new: ponteiro nil
var p2 *int // p2 == nil
*p2 = 10 // PANIC: nil pointer dereference
New com diferentes tipos
Tipos primitivos
// Inteiros
intPtr := new(int) // *int → 0
floatPtr := new(float64) // *float64 → 0.0
boolPtr := new(bool) // *bool → false
strPtr := new(string) // *string → ""
Structs
type Pessoa struct {
Nome string
Idade int
Ativo bool
}
p := new(Pessoa)
fmt.Printf("%+v\n", *p) // {Nome: Idade:0 Ativo:false}
// Todos os campos estão com zero value
p.Nome = "Ana"
p.Idade = 30
p.Ativo = true
Arrays
arr := new([5]int)
fmt.Println(*arr) // [0 0 0 0 0]
arr[0] = 100
arr[4] = 500
fmt.Println(*arr) // [100 0 0 0 500]
Tipos customizados
type Config struct {
Host string
Port int
Debug bool
Timeout time.Duration
}
cfg := new(Config)
// Zero values úteis:
// Host: "" (será preenchido depois)
// Port: 0 (pode significar "usar padrão")
// Debug: false (produção por padrão)
// Timeout: 0 (sem timeout ou usar padrão)
Esse é um exemplo do princípio “zero values úteis” em Go — o design de structs deve considerar que campos não inicializados terão comportamento razoável.
New vs &T{} (composite literal)
Na grande maioria dos casos, desenvolvedores Go preferem &T{} sobre new(T):
// Usando new — retorna ponteiro para valor zerado
cfg1 := new(Config)
cfg1.Host = "localhost"
cfg1.Port = 8080
// Usando &T{} — inicializa campos no mesmo passo
cfg2 := &Config{
Host: "localhost",
Port: 8080,
}
// Ambos produzem *Config, mas &T{} é mais conciso
Quando preferir &T
// Inicialização com valores conhecidos
servidor := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// Structs pequenos com poucos campos
ponto := &Point{X: 10, Y: 20}
// Criando slices de ponteiros
usuarios := []*Pessoa{
{Nome: "Ana", Idade: 25},
{Nome: "Bruno", Idade: 30},
}
Quando new pode ser preferível
// Quando quer explicitamente o zero value sem inicializar nada
buffer := new(bytes.Buffer) // pronto para uso — zero value funcional
// Tipos onde o zero value é o estado desejado
mu := new(sync.Mutex) // mutex desbloqueado — pronto para uso
// Quando trabalha com tipos genéricos
func criar[T any]() *T {
return new(T)
}
Na prática, new é mais comum em código que trabalha com reflection, generics ou quando o zero value é exatamente o que você precisa sem nenhuma personalização.
New vs Make — diferença fundamental
Essa é uma das perguntas mais comuns de quem está aprendendo Go. A diferença é clara:
| Característica | new(T) | make(T, args...) |
|---|---|---|
| Tipos aceitos | Qualquer tipo | Apenas slice, map, channel |
| Retorna | *T (ponteiro) | T (valor) |
| Inicializa | Zero value apenas | Estrutura interna completa |
| Uso principal | Obter ponteiro | Criar coleções prontas |
// new com slice: retorna *[]int apontando para nil slice
p := new([]int)
fmt.Println(*p == nil) // true — INUTILIZÁVEL para append!
// make com slice: retorna []int inicializado
s := make([]int, 0, 10)
fmt.Println(s == nil) // false — pronto para uso
s = append(s, 1, 2, 3) // funciona perfeitamente
Regra prática: use make para slices, maps e channels. Para tudo mais, &T{} ou new(T).
Alocação stack vs heap
Uma dúvida comum é se new sempre aloca no heap. A resposta é: não necessariamente. O compilador Go realiza escape analysis para determinar onde alocar:
func exemploStack() {
// Se p não escapa da função, pode ficar na stack
p := new(int)
*p = 42
fmt.Println(*p) // fmt.Println faz p escapar para o heap
}
func exemploSemEscape() int {
// Compilador pode alocar na stack — mais rápido
p := new(int)
*p = 42
return *p // valor copiado, p não escapa
}
Use go build -gcflags="-m" para ver as decisões de escape analysis:
$ go build -gcflags="-m" main.go
./main.go:5:10: new(int) does not escape
./main.go:11:10: new(int) escapes to heap
Na prática, não se preocupe prematuramente com stack vs heap — o compilador Go é sofisticado o suficiente para otimizar a maioria dos casos. Foque em escrever código correto e legível, e use profiling quando precisar otimizar.
Padrões com new no código real
Zero value funcional — design idiomático
Go incentiva projetar tipos cujo zero value seja imediatamente útil:
// sync.Mutex — zero value = desbloqueado
var mu sync.Mutex // pronto para Lock()/Unlock()
// bytes.Buffer — zero value = buffer vazio
var buf bytes.Buffer // pronto para Write()
buf.WriteString("olá")
// http.Client — zero value = cliente com defaults razoáveis
var client http.Client // pronto para Get(), Post()
Quando seu struct segue esse padrão, new ou declaração sem inicialização funcionam perfeitamente:
type Logger struct {
Prefix string // "" = sem prefixo
Level int // 0 = nível mais baixo (debug)
Output io.Writer // nil = será tratado como os.Stdout
}
func (l *Logger) writer() io.Writer {
if l.Output == nil {
return os.Stdout
}
return l.Output
}
Constructor pattern — quando new não basta
Para tipos que precisam de inicialização complexa, o padrão Go é criar uma func construtora:
type Servidor struct {
host string
port int
handler http.Handler
logger *log.Logger
}
// Constructor — convenção New + nome do tipo
func NewServidor(host string, port int) *Servidor {
return &Servidor{
host: host,
port: port,
logger: log.New(os.Stdout, "[srv] ", log.LstdFlags),
}
}
// Options pattern para configuração flexível
type Option func(*Servidor)
func WithLogger(l *log.Logger) Option {
return func(s *Servidor) {
s.logger = l
}
}
func NewServidorComOpcoes(host string, port int, opts ...Option) *Servidor {
s := &Servidor{host: host, port: port}
for _, opt := range opts {
opt(s)
}
return s
}
Note que construtores usam &T{} ao invés de new(T) — isso é padrão no ecossistema Go.
New em generics
Com a introdução de generics no Go 1.18+, new ganhou um novo caso de uso:
func Zero[T any]() T {
var zero T
return zero
}
func Ponteiro[T any]() *T {
return new(T) // funciona com qualquer tipo
}
func PonteiroParaValor[T any](v T) *T {
return &v
}
// Útil para campos opcionais
type Request struct {
Nome string
Limite *int // nil = sem limite
}
// Helper genérico
func Ptr[T any](v T) *T {
return &v
}
req := Request{
Nome: "busca",
Limite: Ptr(100), // converte int para *int elegantemente
}
New e interfaces
Um detalhe importante: new com interfaces não faz sentido e resulta em erro de compilação:
// ERRO: cannot use new with interface type
// p := new(io.Reader) // não compila!
// Interfaces são satisfeitas por tipos concretos
var r io.Reader = new(bytes.Buffer) // OK: *bytes.Buffer implementa io.Reader
Interfaces em Go são tipos abstratos — você não pode instanciá-las diretamente. O que você pode fazer é criar um valor concreto que satisfaz a interface e atribuí-lo.
Boas práticas com new
- Prefira
&T{}na maioria dos casos — mais idiomático e permite inicialização - Use new quando quer zero value explícito — deixa a intenção clara
- Projete zero values úteis — permita que
new(T)retorne algo funcional - Crie funções construtoras (
NewXxx) para inicialização complexa - Nunca use new para slices, maps ou channels — use make
- Não se preocupe com stack vs heap — o compilador otimiza isso por você
Para complementar seus estudos, explore como ponteiros funcionam em Go e como o garbage collector gerencia memória alocada com new e &T{}.
Perguntas frequentes (FAQ)
Qual a diferença entre new(T) e &T{} em Go?
Ambos retornam *T, mas &T{} permite inicializar campos simultaneamente enquanto new(T) sempre retorna um ponteiro para o zero value. Na prática, &T{} é mais usado porque combina alocação e inicialização. Use new quando quer explicitamente o zero value ou em contextos genéricos onde &T{} não é possível.
New aloca no heap ou na stack?
Depende. O compilador Go realiza escape analysis para decidir. Se o ponteiro retornado por new não escapa da func (não é retornado, passado para outra goroutine, ou armazenado em variável de escopo externo), o compilador pode alocar na stack, que é mais rápido. Use go build -gcflags="-m" para verificar.
Por que new é pouco usado em código Go?
Porque &T{} (endereço de literal composto) oferece a mesma funcionalidade com a vantagem adicional de permitir inicialização de campos. Além disso, muitos tipos em Go têm zero values funcionais, eliminando a necessidade de alocação explícita — basta declarar var x T. Para slices, maps e channels, make é a escolha correta.
Posso usar new com tipos de interface?
Não. Interfaces são tipos abstratos em Go e não podem ser instanciadas com new. Você precisa criar um valor de um tipo concreto que satisfaz a interface. Por exemplo, var r io.Reader = new(bytes.Buffer) funciona porque *bytes.Buffer implementa io.Reader, mas new(io.Reader) não compila.