Go has a special make
function that can be used to initialize channels, slices, and maps.
Using make
, we can specify the memory and capacity constraints of the data type being created, giving us low-level control that’s not available to us using regular constructor functions
Basic Usage
make
is a special function in Go that can take a different number of types and arguments.
It returns an instance of the type passed as the first argument:
obj := make(someType, optionalArgument1, optionalArgument2)
Here, someType
can be a slice, a map, or a channel.
Creating Slices
We can initialize slices of any type using make
:
words := make([]string, 2)
Here, the first argument is the type and the second argument is the length.
By default, a new slice is initialized and filled with as many empty values as the length specified.
So, in this case, the value of words
would be []string{"", ""}
We can also pass a third argument when creating a slice, which is the capacity. The capacity denotes how much memory is allocated to a slice, even though its length may not be as much.
For example, if we create words
using capacity as well:
words := make([]string, 2, 5)
the value of words
now is still []string{"", ""}
, but the underlying memory is allocated for 5 string values.
So, if we add another element using append
, Go doesn’t allocate any more memory under the hood:
words = append(words, "lorem ipsum")
By default, if you don’t assign any capacity, Go assumes a default capacity. When appending more items, Go provisions more capacity as and when needed.
The capacity argument has to be greater than the length argument, otherwise the code will not build
So, specifying the capacity is very useful if you know how big your slice will be beforehand, since we can skip the extra allocation each time the default capacity is exceeded.
Note that specifying the capacity doesn’t cap the max limit, but rather provisions the initial capacity that needs to be re-allocated when more elements are added
Creating Maps
Using make
with maps is not as straightforward as with slices.
// make an empty map
m := make(map[int]string)
// make a map with optional capacity for `n` elements
m := make(map[int]string, n)
We can still make empty map instances, and specify the capacity, but the capacity here is taken by the language as a hint, and doesn’t make any guarantees about the exact capacity allocated.
The language specification itself mentions that the second argument describes “initial space for approximately n elements”
Creating Channels
We can create different types of channels using make:
- Unbuffered channels, that cannot store any data, and only act as a data pipe:
// create an unbuffered channel of integers out := make(chan int)
- Buffered channels which can store some quantity of data
// create a buffered channel, that can hold a maximum of 3 integer values out := make(chan int, 3)
Make vs New
Go also has a built in function called new
, which often appears in similar scenarios as make
, but has different functionality.
While make
is able to allocate variable memory and returns an instance of the provided type, new
can only initialize empty instances, and returns a pointer of the provided argument.
Let’s take a look at an example:
// returns a slice with 1 default (0) int element
s1 := make([]int, 1)
// returns a pointer to an empty slice
s2 := new([]int)
fmt.Println(s1)
fmt.Println(s2)
If we run this code, we will get:
[0]
&[]
Conclusion
make
is a versatile built-in function that can be used to initialize different data types.
The arguments and their significance depends on the type of variable being initialized.
You can read more about the details of how make
and new
work in the language specification.