Allocation

Go also has garbage collection, meaning that you don’t have to worry aboutmemory deallocation.12

To allocate memory Go has two primitives, new and make. They do differentthings and apply to different types, which can be confusing, but the rules aresimple. The following sections show how to handle allocation in Go and hopefullyclarifies the somewhat artificial distinction between new and make .

Allocation with new

The built-in function new is essentially the same as its namesakes in otherlanguages: new(T) allocates zeroed storage for a new item of type T andreturns its address, a value of type *T. Or in other words, it returnsa pointer to a newly allocated zero value of type T. This is important toremember.

The documentation for bytes.Buffer states that “the zero value for Buffer isan empty buffer ready to use.”. Similarly, sync.Mutex does not have anexplicit constructor or Init method. Instead, the zero value for a sync.Mutexis defined to be an unlocked mutex.

Allocation with make

The built-in function make(T, args) serves a purpose different from new(T).It creates slices, maps, and channels only, and it returns an initialized (notzero!) value of type T, and not a pointer: *T. The reason for thedistinction is that these three types are, under the covers, references to datastructures that must be initialized before use. A slice, for example, isa three-item descriptor containing a pointer to the data (inside an array), thelength, and the capacity; until those items are initialized, the slice is nil.For slices, maps, and channels, make initializes the internal data structureand prepares the value for use.

For instance, make([]int, 10, 100) allocates an array of 100 ints and thencreates a slice structure with length 10 and a capacity of 100 pointing at thefirst 10 elements of the array. In contrast, new([]int) returns a pointer toa newly allocated, zeroed slice structure, that is, a pointer to a nil slicevalue. These examples illustrate the difference between new and make.

  1. var p *[]int = new([]int) 1
  2. var v []int = make([]int, 100) 2
  3. var p *[]int = new([]int) 3
  4. *p = make([]int, 100, 100)
  5. v := make([]int, 100) 4

Allocates 1 slice structure; rarely useful. v 2 refers to a new array of100 ints. At 3 we make it unnecessarily complex, 4 is more idiomatic.

Remember that make applies only to maps, slices and channels and does notreturn a pointer. To obtain an explicit pointer allocate with new.

new allocates; make initializes.

The above two paragraphs can be summarized as:

  • new(T) returns *T pointing to a zeroed T
  • make(T) returns an initialized T

And of course make is only used for slices, maps and channels.

Constructors and composite literals

Sometimes the zero value isn’t good enough and an initializing constructor isnecessary, as in this example taken from the package os.

  1. func NewFile(fd int, name string) *File {
  2. if fd < 0 {
  3. return nil
  4. }
  5. f := new(File)
  6. f.fd = fd
  7. f.name = name
  8. f.dirinfo = nil
  9. f.nepipe = 0
  10. return f
  11. }

There’s a lot of boiler plate in there. We can simplify it using acomposite literal, which is an expression that creates a new instance each time it is evaluated.

  1. func NewFile(fd int, name string) *File {
  2. if fd < 0 {
  3. return nil
  4. }
  5. f := File{fd, name, nil, 0}
  6. return &f 1
  7. }

It is OK to return the address of a local variable 1 the storage associatedwith the variable survives after the function returns.

In fact, taking the address of a composite literal allocates a fresh instanceeach time it is evaluated, so we can combine these last two lines.13

  1. return &File{fd, name, nil, 0}

The items (called fields) of a composite literal are laid out in order and mustall be present. However, by labeling the elements explicitly as field:valuepairs, the initializers can appear in any order, with the missing ones left astheir respective zero values. Thus we could say

  1. return &File{fd: fd, name: name}

As a limiting case, if a composite literal contains no fields at all, it createsa zero value for the type. The expressions new(File) and &File{} areequivalent. In fact the use of new is discouraged.

Composite literals can also be created for arrays, slices, and maps, with thefield labels being indices or map keys as appropriate. In these examples, theinitializations work regardless of the values of Enone, and Einval, as longas they are distinct:

  1. ar := [...]string{Enone: "no error", Einval: "invalid argument"}
  2. sl := []string{Enone: "no error", Einval: "invalid argument"}
  3. ma := map[int]string {Enone: "no error", Einval: "invalid argument"}