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.
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:
- We start by creating a pointer to an
int
variable (numPtr
) usingnew(int)
. - We use
unsafe.Pointer
to convert thenumPtr
(a pointer toint
) into anunsafe.Pointer
type. - Then, we use
uintptr
to convert theunsafe.Pointer
value into auintptr
type. - 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.
- We start by creating a slice
slice
with a length of 3 usingmake([]int, 3)
. - We then use
reflect.ValueOf
to convert the slice into areflect.Value
type. - Next, we use the
Elem
method to access the elements of the slice. - We further use
UnsafeAddr
to retrieve the pointer to the underlying array of the slice. - 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, whilemake()
is specifically used for creating variables of reference types.new()
returns a pointer to the zero value of the specified type, whilemake()
returns an initialized value of the specified reference type.- Variables created using
new()
are set to their zero values, while variables created withmake()
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.