Memory Allocation in Go: make, new and var
In Go, there are several ways to allocate and initialize memory.
Pre-requisite: undertsand types in Go
var Declaration
When you declare a variable using var:
var x int
var s string
var m map[string]int
var sl []int
- The variable is zero-valued (0 for numeric types, "" for strings, nil for pointers, maps, slices, etc.)
- For primitive types (int, string, etc.), memory is allocated directly
- For reference types (slices, maps, channels), only the header structure is created, but not the underlying data structure - they're initialized to nil
new() Function
new() allocates memory but doesn't initialize it:
x := new(int)
m := new(map[string]int) // m points to a nil map
- Memory is allocated for that specific type (so the type is known)
- The memory is zero-initialized (filled with zeros)
- Returns a pointer to a newly allocated, zero-valued variable
- Memory is allocated but the underlying data structure isn't initialized** (for composite reference types)
- For maps, slices, and channels, this means you get a non-nil pointer to a nil value
make() Function
make() allocates AND initializes memory:
m := make(map[string]int)
s := make([]int, 5, 10)
ch := make(chan int, 10)
- Only works for slices, maps, and channels
- Allocates and initializes the underlying data structure**
- Returns the initialized (non-pointer) value itself, not a pointer
- Can specify capacity and length for slices and buffered channels
Key Differences
What they return:
- var declares a variable with its zero value
- new() returns a pointer to a zero-valued variable
- make() returns an initialized (non-pointer) value
What they can be used for:
- var can be used for any type
- new() can be used for any type
- make() can only be used for slices, maps, and channels
Initialization:
- var and new() only zero-initialize
- make() properly initializes the underlying data structure
In Gist
// Using var
var i int // i = 0
var s string // s = ""
var m map[string]int // m = nil, not usable yet!
// Using new()
pi := new(int) // pi is a *int pointing to 0
pm := new(map[string]int) // pm is a *map[string]int pointing to nil map
// (*pm)["key"] = 1 would panic!
// Using make()
m2 := make(map[string]int) // m2 is an initialized, empty map
m2["key"] = 1 // works fine
sl := make([]int, 3, 5) // slice with length 3, capacity 5
** When we say Memory is allocated but the underlying data structure isn't initialized- What do we mean?
📌📌 We refer to specific situation where we are working on types that:
- Are composite (composed of other types)
- Have reference semantics (pass by reference)
- Require internal data structure initialization
which would be slices, maps and channels.
When we use new()
on a map:
- Memory is allocated for the map header (so Go knows it's a map)
- The header is zero-initialized, which means the pointer to the underlying hash table is set to nil
- We get a pointer to this header
The map header exists and has a type, but it points to nothing (nil). That's why we say the underlying data structure isn't initialized. The map exists as a variable with the correct type, but it's not ready to be used for storing key-value pairs.
When we use make() for slices, maps, and channels, we're initializing the underlying data structures that these reference types point to, making them ready for use.
m := new(map[string]int) // m points to a map header, but the map itself is nil
// (*m)["key"] = 1 // This would panic because the underlying hash table doesn't exist yet
m := make(map[string]int) // Creates the map header AND initializes the hash table
m["key"] = 1 // This works fine
Other composite types that don't have this dual nature (like structs and arrays) don't need make() - they can be used immediately when declared with var or can be allocated with new() without further initialization.