Just take a quick look at the go variable initialization, basically, there are four ways to initialize variables in Go.
1. Zero Value Initialization
Go provides zero values for all types as a safety feature - no uninitialized memory.
var i int // 0
var s string // ""
var b bool // false
var p *int // nil
var slice []int // nil
var m map[string]int // nil
var ch chan int // nil
2. Direct Expression Initialization
// Basic types
var i int = 42
var s string = "hello"
var b bool = true
// Composite types
var arr [3]int = [3]int{1, 2, 3}
var slice []int = []int{1, 2, 3}
var m map[string]int = map[string]int{"key": 1}
// Short declaration
i := 42
s := "hello"
slice := []int{1, 2, 3}
3. make() - For Reference Types Only
make() can only be used with slice, map, and channel. It allocates and initializes the underlying data
structure. So the object returned by make() is ready to use and won’t be nil forever.
- Returns a value, not a pointer.
- Returns an initialized and usable object
- Allocate memory internally and performs initialization.
// slice
slice1 := make([]int, 5) // length 5, capacity 5, zero values
slice2 := make([]int, 3, 10) // length 3, capacity 10, zero values
// map
m1 := make(map[string]int) // empty map, ready to use
m2 := make(map[string]int, 10) // map with pre-allocated capacity
// channel
ch1 := make(chan int) // unbuffered channel
ch2 := make(chan int, 5) // buffered channel with capacity 5
Here are some pesudo implementations of make() for different types:
// make([]int, 3, 5)
func makeSlice(et *_type, len, cap int) slice {
// calculate memory size
mem := mallocgc(et.size * cap, et, true)
// creative slice header
return slice{
array: mem,
len: len,
cap: cap,
}
}
// make(map[string]int)
func makeMap(t *maptype, hint int) *hmap {
h := new(hmap)
h.hash0 = fastrand()
if hint > 0 {
h.buckets = mallocgc(...)
}
return h
}
// make(chan int, 3)
func makeChan(t *chantype, size int) *hchan {
c := new(hchan)
c.dataqsiz = uint(size)
c.elemsize = uint16(t.elem.size)
if size > 0 {
c.buf = mallocgc(...)
}
return c
}
4. new() - For All Types
new(T) works with any type T,
- Returns a pointer
- Points to zero value
- No additional initialization performed
// Basic types
pi := new(int) // *int, pointer to zero value 0
ps := new(string) // *string, pointer to zero value ""
// Composite types
type Person struct {
Name string
Age int
}
p1 := new(Person) // *Person, pointer to zero value Person{Name: "", Age: 0}
p2 := new([]int) // *[]int, pointer to nil slice
p3 := new(map[string]int) // *map[string]int, pointer to nil map
new() isn’t much useful for reference types like slices, maps, and channels since they need initialization to be
usable. Take map as an example:
m1 := new(map[string]int) // *map[string]int, pointer to nil map
(*m1)["key"] = 1 // panic: assignment to entry in nil map
// if we want to use this make, we need to use make to initialize it first
*m1 = make(map[string]int)
(*m1)["key"] = 1 // works fine now
new() is more commonly used for structs:
type Person struct {
Name string
Age int
Email string
}
func main() {
p1 := new(Person)
fmt.Printf("p1 type: %T\n", p1) // *main.Person
fmt.Printf("p1 address: %p\n", p1) // 0x... (pointer address)
fmt.Printf("p1 points to: %+v\n", *p1) // {Name: Age:0 Email:}
// we can use the p1 directly
p1.Name = "Alice"
p1.Age = 25
p1.Email = "alice@example.com"
}