web123456

Go-25 Differences between the make and new keywords and how they are implemented

When we want to be inGo languageWhen you initialize a structure, you use two completely different keywords, make and new. The presence of two initialization keywords at the same time can be very confusing for beginners, but they have completely different roles.

In Go, the main purpose of the make keyword is to initialize built-in data structures, namely arrays, slices, and channels, which we mentioned earlier, while the new keyword can be used when we want to get a pointer to a certain type, except that fewer people know how to use new, so let's introduce the difference between make and new and how it's implemented. Here we will introduce the difference between make and new and how they are implemented.

summarize

Although both make and new can be used to initialize data structures, the types of structures they can initialize are quite different. make can only be used to initialize basic types in Go:

  1. slice := make([]int, 0, 100)
  2. hash := make(map[int]bool, 10)
  3. ch := make(chan int, 5)

These basic types are provided for us by the language, and we have already described the process of their initialization and the principle, but it is still important to draw attention to the fact that these three return different types of data structures:

  • slice is a structure containing data, cap and len;
  • hash is a pointer to an hmap structure;
  • ch is a pointer to the hchan structure.


The other keyword, new, which is used to initialize a data structure, is really quite simple: it takes a type as a parameter and returns a pointer to that type:

  1. i := new(int)
  2. var v int
  3. i := &v

The two different initialization methods in the above code snippet are actually equivalent in that they both create a pointer to the zero value of int.

By this point we have a good understanding of the use of these two different keywords in the Go language:make is used to create built-in data structures such as slices, hash tables, and pipes, and new is used to allocate and create a pointer to the corresponding type.

Implementation Principle

In the next section, we'll describe how make and new are used to initialize different data structures, and we'll understand the principles of these two keywords at both compile-time and run-time.

make

We've already seen how make creates arrays and slices, hash tables, and channels, so we'll only briefly touch on the initialization of make's data structures here.

During the type-checking phase during compilation, the Go language actually converts the OMAKE node representing the make keyword into three different types of nodes, OMAKESLICE, OMAKEMAP, and OMAKECHAN, depending on the type of the argument, and these nodes end up calling different runtime functions to initialize the data structures.

new

The built-in function new is processed by the callnew function at the SSA code generation stage during compilation, and if the size of the type requested to be created is 0, a zerobase variable representing a null pointer is returned, and the keyword is converted to a newobject in all other cases:

  1. func callnew(t *) *Node {
  2. if () {
  3. yyerror("%v is go:notinheap; heap allocation disallowed", t)
  4. }
  5. dowidth(t)
  6. if () == 0 {
  7. z := newname(("zerobase"))
  8. (PEXTERN)
  9. = t
  10. return typecheck(nod(OADDR, z, nil), ctxExpr)
  11. }
  12. fn := syslook("newobject")
  13. fn = substArgTypes(fn, t)
  14. v := mkcall1(fn, (t), nil, typename(t))
  15. (true)
  16. return v
  17. }

It should be mentioned that even if the current variable is initialized with var, at this stage it may be converted to a newobject function call and request memory on the heap:

  1. func walkstmt(n *Node) *Node {
  2. switch {
  3. case ODCL:
  4. v :=
  5. if () == PAUTOHEAP {
  6. if prealloc[v] == nil {
  7. prealloc[v] = callnew()
  8. }
  9. nn := nod(OAS, , prealloc[v])
  10. (true)
  11. nn = typecheck(nn, ctxStmt)
  12. return walkstmt(nn)
  13. }
  14. case ONEW:
  15. if == EscNone {
  16. r := temp(())
  17. r = nod(OAS, r, nil)
  18. r = typecheck(r, ctxStmt)
  19. (r)
  20. r = nod(OADDR, , nil)
  21. r = typecheck(r, ctxExpr)
  22. n = r
  23. } else {
  24. n = callnew(())
  25. }
  26. }
  27. }

Of course this is not absolute, if the currently declared variable or parameter does not need to live outside the current scope, then it will not actually be initialized on the heap, but will be initialized on the stack of the current function and destroyed with the end of the function call.

The job of the newobject function is to get the size of the incoming type and call mallocgc to claim a decent-sized piece of memory on the heap and return a pointer to it:

  1. func newobject(typ *_type) {
  2. return mallocgc(, typ, true)
  3. }

summarize

Finally, to briefly summarize the implementation of the make and new keywords in Go, the main purpose of the make keyword is to create built-in data structures such as slices, hash tables, and channels, while the main purpose of new is to request a piece of memory for a type and return a pointer to that memory.