When you declare a variable, where T
is some type:
var name T
Go gives you a piece of uninitialized "zeroed" memory.
With primitives, this means that var name int
would be 0, and var name string
would be "". In C it might be zeroed, or might be something unexpected. Go guarantees an uninitialized variable is the type's zero equivalent.
Internally slices, maps, and channels are treated as pointers. Pointers zero value is nil, meaning it points to nil memory. Without initializing it, you can encounter a panic if you try to operate on it.
The make
function is specifically designed for a slice, map, or channel. The make function's arguments are:
make(T type, length int[, capacity int]) // For slices.
make(T[, capacity int]) // For a map.
make(T[, bufferSize int]) // For a channel. How many items can you take without blocking?
A slices length
is how many items it starts with. The capacity is the allocated memory before a resize is needed (internally, new size * 2, then copy). For more information see Effective Go: Allocation with make.
Structs: new(T)
is equivalent to &T{}
, not T{}
. *new(T)
is equivalent to *&T{}
.
Slices: make([]T,0)
is equivalent to []T{}
.
Maps: make(map[T]T)
is equivalent to map[T]T{}
.
As far as which method is preferred, I ask myself the following question:
Do I know the value(s) right now inside the function?
If the answer is "yes", then I go with one of the above T{...}
. If the answer is "no", then I use make or new.
For example, I would avoid something like this:
type Name struct {
FirstName string
LastName string
}
func main() {
name := &Name{FirstName:"John"}
// other code...
name.LastName = "Doe"
}
Instead I would do something like this:
func main() {
name := new(Name)
name.FirstName = "John"
// other code...
name.LastName = "Doe"
}
Why? Because by using new(Name)
I make it clear that I intend to fill the values later. If I used &Name{...}
it wouldn't be clear that I intended to add/change a value later in the same function without reading the rest of the code.
The exception is with structs when you don't want a pointer. I'll use T{}
, but I won't put anything in it if I plan to add/change the values. Of course *new(T)
also works, but that's like using *&T{}
. T{}
is cleaner in that case, although I tend to use pointers with structs to avoid making a copy when passing it around.
Another thing to keep in mind, a []*struct
is smaller and cheaper to resize than []struct
, assuming the struct is much larger than a pointer, which is typically 4 - 8 bytes (8 bytes on 64bit?).