avatarDwen

Summarize

Demystifying Golang’s new() and make(): Understanding the Key Differences

Discover the nuanced distinctions between Golang’s new() and make() functions and learn when and how to utilize each for effective memory management in your Go applications.

Photo by Tom Winckels on Unsplash

In the Go programming language, new() and make() are two commonly used functions for creating and initializing variables of different types.

This blog will delve into the differences, use cases, and underlying implementation details of new() and make().

The new() and make() functions in Go are essential tools for creating and initializing variables.

new() is used to create a variable of a specified type with a zero value and returns a pointer to that variable.

make() is used to create and initialize variables of reference types, such as slices, maps, and channels.

Variable declaration.

var i int
var s string

Variable declaration can be done using the var keyword, and these variables can be used in the program.

When we don’t specify initial values for variables, their default values are their zero values.

For example, the zero value for an int is 0, for a string is an empty string (""), and for reference types like slices, maps, and channels, the zero value is nil.

For the two types of declarations in the example, we can use them directly and assign values for output. But what if we switch to reference types?

package main
import (
    "fmt"
)
func main() {
    var i *int
    *i=10
    fmt.Println(*i)
}

What will this example print 0 or 10?

All of the above are incorrect. When you run it, it will panic for the following reasons:

panic: runtime error: invalid memory address or nil pointer dereference

From this hint, it becomes evident that for reference types, we not only need to declare them but also allocate memory for their content; otherwise, where would our values go? This is the reason for the error message mentioned above.

For value types, allocation is not required because memory has already been allocated by default.

To allocate memory, need to use new() and make() .

What is new() ?

new() is used for creating variables of types other than reference types.

package main
import "fmt"
func main() {
    numPtr := new(int)
    // Result: 0
    fmt.Println(*numPtr)
}

The new() function utilizes Go's runtime.newobject function at the low level.

The runtime.newobject function allocates a block of memory with a size equal to the specified type's size and initializes that memory to zero.

Then, the runtime.newobject function returns a pointer to this block of memory.

Here’s a simplified version of the underlying implementation of the new() function:

package main
import (
   "fmt"
   "unsafe"
)
func main() {
   // To create a pointer to a zero-value int using new()
   numPtr := new(int)
  
   // To retrieve the value of the pointer
   ptrValue := uintptr(unsafe.Pointer(numPtr))
  
   fmt.Println(ptrValue)
}

In the example code provided, we used types from the unsafe package, namely Pointer and uintptr, to work with pointers. Here's a breakdown of the code:

  1. We start by creating a pointer to an int variable (numPtr) using new(int).
  2. We use unsafe.Pointer to convert the numPtr (a pointer to int) into an unsafe.Pointer type.
  3. Then, we use uintptr to convert the unsafe.Pointer value into a uintptr type.
  4. Finally, we print the value of the pointer, which represents the memory address of the variable we created.

What is make() ?

make() is used to create and initialize variables of reference types, such as slices, maps, and channels.

It is suitable for creating these variables because they are not set to zero values but are initialized based on their types.

package main
import "fmt"
func main() {
    slice := make([]int, 3)
    // Result: [0 0 0]
    fmt.Println(slice)
}

The make() function leverages the Go runtime's runtime.makeslice, runtime.makemap, and runtime.makechan functions at the low level.

  • The runtime.makeslice function is used to create slices. It allocates a contiguous block of memory and returns a slice structure.
  • The runtime.makemap function is used to create maps. It allocates memory for a hash table and returns a map structure.
  • The runtime.makechan function is used to create channels. It allocates memory for a channel and returns a channel structure.

Here’s a simplified version of the underlying implementation of the make() function:

package main
import (
   "fmt"
   "reflect"
   "unsafe"
)
func main() {
   slice := make([]int, 3)
  
   sliceValue := reflect.ValueOf(slice)
   sliceData := sliceValue.Elem().UnsafeAddr()
   sliceLen := sliceValue.Len()
  
   fmt.Println(sliceData, sliceLen)
}

In the example code provided, we used methods from the reflect package, including Value, Elem, UnsafeAddr, and Len, to work with slices.

  1. We start by creating a slice slice with a length of 3 using make([]int, 3).
  2. We then use reflect.ValueOf to convert the slice into a reflect.Value type.
  3. Next, we use the Elem method to access the elements of the slice.
  4. We further use UnsafeAddr to retrieve the pointer to the underlying array of the slice.
  5. Finally, we use the Len method to get the length of the slice.

Please note that the example code provided above uses the reflect and unsafe packages.

This is introduced for the purpose of demonstrating the underlying implementation of make().

In practical development, there is usually no frequent need to use these packages.

Let’s revisit the example from the beginning of the blog and see how to resolve the error.

Now that we know it’s because memory hasn’t been allocated for it, let’s allocate it using new().

func main() {
    var i *int
    i=new(int)
    *i=10
    fmt.Println(*i)
}

Now, when you run the program again, it should work perfectly, and it will print 10.

Summary.

  • new() is used to create variables of any type, while make() is specifically used for creating variables of reference types.
  • new() returns a pointer to the zero value of the specified type, while make() returns an initialized value of the specified reference type.
  • Variables created using new() are set to their zero values, while variables created with make() are initialized based on their type.

Finally, it’s important to note that both new() and make() allocate memory on the heap.

In practical coding, new() is seldom used because we typically rely on short variable declarations and struct literals, which are more concise and convenient and avoid the complexities associated with pointers.

The make() function, on the other hand, is indispensable. When working with slices, maps, and channels, it's essential to use make() for initialization before performing operations on them.

If you like such stories and want to support me, please give me a clap.

Your support is very important to me, thank you.

If you enjoyed this article, consider trying out the AI service I recommend. It provides the same performance and functions to ChatGPT Plus(GPT-4) but more cost-effective, at just $6/month (Special offer for $1/month). Click here to try ZAI.chat.

Golang
Web Development
Web
Programming
Development
Recommended from ReadMedium