Back to blog

Saturday, August 15, 2026

Why Does new() Behave Differently From a Struct Literal in Go?

Why Does new() Behave Differently From a Struct Literal in Go?

Why Does new() Behave Differently From a Struct Literal in Go?

new(T) allocates a zero T and returns *T. A struct literal T{...} produces a value of type T. &T{...} takes the address of that value — that form is what you want when you need a pointer and field values. new(T) cannot take field initializers. new(T) is also not nil; var p *T is.

For a zero struct, new(User) and &User{} are the same idea. The confusion is mixing those with User{} (a value) and with new on maps.

The failure

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    a := User{Name: "Ada"}     // User
    b := new(User)             // *User, Name == "", Age == 0
    c := &User{Name: "Ada"}    // *User, Name == "Ada"

    fmt.Printf("%T %+v\n", a, a)
    fmt.Printf("%T %+v\n", b, b)
    fmt.Printf("%T %+v\n", c, c)

    // new(User{Name: "Ada"}) // does not compile
}

People then write new(map[string]int) and panic on write: new zeroed a nil map, it did not make a hash table.

m := new(map[string]int)
(*m)["k"] = 1 // panic: assignment to entry in nil map

Why this happens

new is not a constructor. The spec: allocate a variable of type T, zero it, return its address. No constructor arguments. For a struct that means every field is the type's zero: "", 0, nil pointers/slices/maps.

A composite literal builds the value in one expression. Keyed fields you omit are still zero; fields you name are set. Prefix & and the type of the expression is *T. Go's sugar for &User{Name: "Ada"} is "create the value, take its address." Same as:

u := User{Name: "Ada"}
p := &u

except the composite form does not force you to name the temporary.

var p *User does not call new. It leaves p == nil. Dereferencing that panics. new(User) never returns a nil pointer.

make is a different builtin. It initializes the runtime header for slices, maps, and channels. new does not.

The fix

  • Need a value: User{Name: "Ada"} (prefer keyed fields).
  • Need a pointer with fields set: &User{Name: "Ada"}.
  • Need a pointer to a zero T (including non-struct types): new(T) or &T{} for structs.
  • Need a usable map/slice/channel: make(...).
u := &User{Name: "Ada", Age: 36}
m := make(map[string]int)
m["k"] = 1

Do not pick new vs &T{} for performance. Escape analysis decides stack vs heap either way.