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ísticanew(T)make(T, args...)
Tipos aceitosQualquer tipoApenas slice, map, channel
Retorna*T (ponteiro)T (valor)
InicializaZero value apenasEstrutura interna completa
Uso principalObter ponteiroCriar 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

  1. Prefira &T{} na maioria dos casos — mais idiomático e permite inicialização
  2. Use new quando quer zero value explícito — deixa a intenção clara
  3. Projete zero values úteis — permita que new(T) retorne algo funcional
  4. Crie funções construtoras (NewXxx) para inicialização complexa
  5. Nunca use new para slices, maps ou channels — use make
  6. 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.