Zero Values and Initialization
Zero Values and Initialization
Every variable in Go has a value from the moment it exists. The boring default is: declare with var when the zero is what you want, assign with := when you have a real value, call make only for slices, maps, and channels you are about to fill.
Mental model
The zero value is the language’s “empty but usable” bit pattern for a type: 0, "", false, nil. You never read uninitialised memory. That is why a missed assignment often looks like a quiet 0 instead of a crash — and why zeros are part of the design, not an accident.
| How | What you get |
|---|---|
var x T |
x is type T, value is zero |
x := v |
type of x is the type of v |
new(T) |
*T pointing at a zero T |
make(slice/map/chan) |
a live header, not nil (for the length you asked) |
new is rarely needed: &Ticket{} is the same idea and names the fields. make is not optional for maps you will write.
Worked examples
Case 1: Zeros you can print
Save as zeros.go. One var per kind, no assignment.
// zeros.go
package main
import "fmt"
type Ticket struct {
ID int
Table int
}
func main() {
var open bool
var tables int
var name string
var ticket *Ticket
var items []string
var prices map[string]int
var t Ticket
fmt.Printf("bool %v\n", open)
fmt.Printf("int %d\n", tables)
fmt.Printf("string %q\n", name)
fmt.Printf("pointer nil=%v\n", ticket == nil)
fmt.Printf("slice nil=%v len=%d\n", items == nil, len(items))
fmt.Printf("map nil=%v len=%d\n", prices == nil, len(prices))
fmt.Printf("struct %+v\n", t)
}Run:
go run zeros.goOutput:
bool false
int 0
string ""
pointer nil=true
slice nil=true len=0
map nil=true len=0
struct {ID:0 Table:0}
A nil slice has length 0. A nil map has length 0. They are not the same as empty-but-allocated, which is what make gives you.
Case 2: := when you already know the value
Save as short_decl.go. Short declaration infers the type from the right-hand side and must introduce at least one new name.
// short_decl.go
package main
import "fmt"
func main() {
name := "Amina"
tables := 3
open := true
fmt.Printf("%s %d open=%v\n", name, tables, open)
}Run:
go run short_decl.goOutput:
Amina 3 open=true
Use var tables int = 3 when you want the type on the left for a reader. Use := when the type is obvious from the value.
Case 3: make for a map and a slice you will fill
Save as make_desk.go. append on a nil slice is fine. A write to a nil map panics (next chapter on maps shows that crash). make the map before the first write.
// make_desk.go
package main
import "fmt"
func main() {
var items []string
items = append(items, "toast")
items = append(items, "tea")
prices := make(map[string]int)
prices["toast"] = 350
prices["tea"] = 250
fmt.Println(items)
fmt.Println(prices["toast"], prices["tea"])
}Run:
go run make_desk.goOutput:
[toast tea]
350 250
make([]string, 0, 8) is the same story with a hint: length 0, capacity 8, so the first few appends do not allocate again.
Case 4: new vs a pointer to a composite literal
Save as new_ticket.go. new(Ticket) returns a pointer to a zero ticket. &Ticket{ID: 7} does the same job and lets you set fields.
// new_ticket.go
package main
import "fmt"
type Ticket struct {
ID int
Table int
}
func main() {
a := new(Ticket)
b := &Ticket{}
c := &Ticket{ID: 7, Table: 12}
fmt.Printf("new %#v nil=%v\n", a, a == nil)
fmt.Printf("&{} %#v\n", b)
fmt.Printf("lit %#v\n", c)
}Run:
go run new_ticket.goOutput:
new &main.Ticket{ID:0, Table:0} nil=false
&{} &main.Ticket{ID:0, Table:0}
lit &main.Ticket{ID:7, Table:12}
a is not nil. It points at zeros. Prefer c when you have data; prefer var t Ticket when a value (not a pointer) is enough.
The trap
Zeros look like data. A function that returns int cannot tell “table 0” from “I forgot to set the table” unless you add an error or a separate ok flag. Save as zero_table.go:
// zero_table.go
package main
import "fmt"
func tableFor(name string) int {
if name == "window" {
return 4
}
return 0
}
func main() {
fmt.Println("window", tableFor("window"))
fmt.Println("missing", tableFor("patio"))
}Run:
go run zero_table.goOutput:
window 4
missing 0
The second line is not a table. It is the zero. The fix is the comma-ok style you will use on maps, or an error: func tableFor(name string) (int, error). Do not invent a sentinel like -1 unless the domain already uses it.
The boring rule
var x Twhen the zero is the right start (counters, flags, buffers youappendto).x := vwhenvis the real value.makemaps before write.makeslices when you know length or capacity.new(T)and&T{}both yield a non-nil*T. Prefer the literal.- Treat
0,"", andnilas values you will see in production, not as “unset” unless you also returnerror. - Nil slice:
appendis safe. Nil map: write is not.
Try this
- In
zeros.go,appendone string ontoitemsand printitems == nilagain. Afterappendit is no longer nil. - In
make_desk.go, replacemake(map[string]int)withvar prices map[string]intand run. Read the panic. - In
new_ticket.go, addvar t Ticketandp := &t. Printp. Same shape asnew, different way to get there. - Change
tableForto return(int, bool)and makemainprint"none"when the bool is false.