new, &T{} and make

new(T) and &T{} both hand back a pointer to a fresh zeroed value; make builds slices, maps and channels and nothing else.

Syntaxnew(T) *T | &T{...} *T | make([]T, len, cap) | make(map[K]V) | make(chan T, n)

Go gives you three ways to create a value, and they are not interchangeable. Knowing which belongs to which type removes a whole class of confusing errors.

new and &T{}

new(T) allocates space for a value of type T, sets every field to its zero value, and returns the address of it: a *T. &T{} does exactly the same thing, written as a composite literal with its address taken. They are the same thing, so prefer the literal, because the literal can also fill fields in: &Point{X: 3, Y: 4}. new stays useful for types that have no fields, such as new(int).

make

make works on exactly three types: slices, maps and channels. A channel is a pipe that carries values between parts of a running program; a later stage uses them. These three need setup beyond zeroing, so make builds the internal structure and returns a usable value of the type itself, not a pointer to it.

  • make([]int, 3) gives a slice of three zeros; make([]int, 0, 3) gives an empty one with room for three.
  • make(map[string]int) gives an empty map; a second argument is a size hint, not a limit.
  • make(chan string, 1) gives a channel that can hold one value.

What trips people up

The zero value of a slice is nil, and append works on a nil slice, so var s []int is often enough. The zero value of a map is also nil, but writing to a nil map stops the program. A map must be made first.

Example

Example · go
package main

import "fmt"

type Point struct {
	X, Y int
}

func main() {
	// new(T) allocates a zeroed T and returns its address.
	a := new(Point)
	// &T{} is the same thing, and can set fields while it is at it.
	b := &Point{}
	c := &Point{X: 3, Y: 4}
	fmt.Println(*a, *b, *c)

	// make is only for slices, maps and channels, and it returns the
	// ready-to-use value itself rather than a pointer to it.
	nums := make([]int, 0, 2)
	nums = append(nums, 7)
	ages := make(map[string]int)
	ages["ada"] = 36
	done := make(chan string, 1)
	done <- "ok"

	fmt.Println(nums, ages["ada"], <-done)
}

// Output:
// {0 0} {0 0} {3 4}
// [7] 36 ok

When to use it

  • Write a NewServer function that returns *Server with defaults already filled in.
  • Pre-size a buffer with make([]byte, 0, 4096) before reading a file into it.
  • Create the map a cache struct needs before anything writes to it.
  • Make a channel with room for one value to carry a completion signal.

More examples

A nil map can be read but not written

This is the shape of the bug behind most "assignment to entry in nil map" reports: a struct has a map field, nobody made it, and reads keep working so the mistake surfaces much later at the first write. When a type owns a map, make it in the function that builds the type.

Example · go
package main

import "fmt"

func main() {
	var counts map[string]int // nil: no storage behind it yet
	// Reading is allowed and gives the zero value for the type.
	fmt.Println(counts == nil, len(counts), counts["missing"])
	// counts["a"] = 1 here would stop the program.

	counts = make(map[string]int, 4) // 4 is a size hint, not a maximum
	counts["a"] = 1
	fmt.Println(counts == nil, len(counts), counts["a"])
}

// Output:
// true 0 0
// false 1 1

Length, capacity, and when make earns its place

Use make([]T, n) when you want n slots to index into straight away, and make([]T, 0, n) when you know roughly how many items you will append and want to avoid repeated regrowth. If you know neither, var s []T plus append is the simplest correct choice.

Example · go
package main

import "fmt"

func main() {
	var zero []int // nil slice, but append works on it
	zero = append(zero, 1)

	sized := make([]int, 3) // three elements, each the zero value 0
	sized[2] = 9

	room := make([]int, 0, 3) // no elements yet, room for three
	room = append(room, 1, 2, 3)

	fmt.Println(zero, sized, len(room), cap(room))
}

// Output:
// [1] [0 0 9] 3 3

A constructor function returning a pointer

This is the standard Go constructor: a plain function named NewX that returns &X{...} with defaults already applied. Reach for it whenever a type has fields that must be set up before use, instead of hoping callers remember to set them.

Example · go
package main

import "fmt"

type Server struct {
	Addr    string
	Timeout int
}

// NewServer returns a pointer, so every caller works on the same Server.
func NewServer(addr string) *Server {
	return &Server{Addr: addr, Timeout: 30}
}

func main() {
	s := NewServer("localhost:8080")
	s.Timeout = 5
	fmt.Println(s.Addr, s.Timeout)

	n := new(int) // new is still the shortest way to get a *int
	*n = 42
	fmt.Println(*n)
}

// Output:
// localhost:8080 5
// 42

Discussion

  • Be the first to comment on this lesson.