Demystifying new() and make() Functions in Go

Demystifying new() and make() Functions in Go

Go (or Golang) is a modern, statically typed, compiled programming language designed for building scalable, concurrent, and efficient software. It comes with various built-in functions and features that help developers write concise and performant code. Among these functions are new() and make(), which might seem similar at first but serve different purposes and are crucial for memory allocation and data initialization in Go.

In this blog post, we will explore the differences between new() and make() functions and understand when and how to use them effectively.

new() and make() Functions

Both new() and make() are built-in functions in Go, used to allocate memory. However, they are used for different data types and scenarios:

  1. new() function:

    • new() is used to allocate memory for value types (e.g., integers, floats, structs) and returns a pointer to the newly allocated zeroed value.

    • It takes a single argument, which is a type, and returns a pointer to that type.

  2. make() function:

    • make() is used to create and initialize slices, maps, and channels, which are reference types in Go.

    • It takes two or three arguments, depending on the type, and returns an initialized (not zeroed) value ready for use.

Understanding new() Function

The syntax of the new() function is straightforward as shown below.

func new(Type) *Type

Here, Type represents the type of the value we want to allocate memory for. Let's see an example of how to use new()

In this example, we create a new instance of the Person struct using new() and then assign values to its fields using the pointer.

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // Using new() to allocate memory for a Person struct
    p := new(Person)

    fmt.Printf("%T\n", p)

    // Accessing struct fields using the pointer
    p.Name = "Alice"
    p.Age = 30

    // Displaying the values
    fmt.Println("Name:", p.Name)
    fmt.Println("Age:", p.Age)
}

This program will produce an output as shown below

> go run main.go
*main.Person
Name: Alice
Age: 30

Understanding make() Function

The syntax of the make() function varies depending on the type it is used with

For Slices

func make([]Type, len, cap) []Type
  • Type: The type of elements the slice will hold.

  • len: The initial length of the slice.

  • cap: The capacity of the slice, which is optional and used to specify the underlying array's capacity. If not provided, it defaults to the same value as the length.

Example of creating a slice using make():

package main

import "fmt"

func main() {
    // Using make() to create a slice of integers
    numbers := make([]int, 5, 10)

    // Displaying the slice's length, capacity, and values
    fmt.Println("Length:", len(numbers))
    fmt.Println("Capacity:", cap(numbers))
    fmt.Println("Values:", numbers)

    // Using make() to create a slice of integers
    numbersWithoutOptional := make([]int, 5)

    // Displaying the slice's length, capacity, and values
    fmt.Println("Length:", len(numbersWithoutOptional))
    fmt.Println("Capacity:", cap(numbersWithoutOptional))
    fmt.Println("Values:", numbersWithoutOptional)
}

This program will produce an output as below

> go run main.go
Length: 5
Capacity: 10
Values: [0 0 0 0 0]
Length: 5
Capacity: 5
Values: [0 0 0 0 0]

For Maps

func make(map[KeyType]ValueType, initialCapacity int) map[KeyType]ValueType
  • KeyType: The type of keys in the map.

  • ValueType: The type of values associated with the keys.

  • initialCapacity: The initial capacity of the map. This is optional but can be used to optimize performance when the number of elements is known in advance.

Example of creating a map using make()

package main

import "fmt"

func main() {
    // Using make() to create a map of string keys and int values
    scores := make(map[string]int)

    // Adding values to the map
    scores["Alice"] = 95
    scores["Bob"] = 87

    // Displaying the map
    fmt.Println("Scores:", scores)
}
> go run main.go
Scores: map[Alice:95 Bob:87]

For Channels

func make(chan Type, capacity int) chan Type
  • Type: The type of values that can be sent and received through the channel.

  • capacity: The buffer size of the channel. If set to 0, the channel is unbuffered.

Example of creating a channel using make()

package main

import (
    "fmt"
    "time"
)

func main() {
    // Using make() to create an unbuffered channel of integers
    ch := make(chan int)

    // Sending data into the channel using a goroutine
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            time.Sleep(time.Second) // Simulating some work before sending the next value
        }
        close(ch)
    }()

    // Receiving data from the channel
    for num := range ch {
        fmt.Println("Received:", num)
    }
}
> go run main.go
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5

Conclusion

In this blog post, we have demystified the new() and make() functions in Go and explained their differences and use cases. To summarize:

  • Use new() to allocate memory for value types and obtain a pointer to the zeroed value.

  • Use make() to create and initialize slices, maps, and channels (reference types) with their respective types and initial capacities.

Understanding the distinctions between new() and make() is crucial for efficient memory allocation and data initialization in Go. Properly applying these functions will lead to cleaner and more optimized code in your Golang projects. Happy coding!

Did you find this article valuable?

Support The Bug Shots by becoming a sponsor. Any amount is appreciated!