Initialization dependencies in Go

Let’s start right away with two simple Go programs:

program#1

package main
import "fmt"
var (
a int = b + 1
b int = 1
)
func main() {
fmt.Println(a)
fmt.Println(b)
}

program#2

package main
import "fmt"
func main() {
var (
a int = b + 1
b int = 1
)
fmt.Println(a)
fmt.Println(b)
}

It would be a rather weak material for a story if both snippets would produce the same result. Fortunately they don’t:

program#1

2
1

program#2

This one doesn’t even build and produces only compile-time error “undefined: b” at line 7.

What exactly underpins this difference? “Normally” initialization expressions in variable declarations are evaluated as you might expect so left-to-right and top-to-bottom:

func f() int { fmt.Println("f"); return 1 }
func g() int { fmt.Println("g"); return 2 }
func h() int { fmt.Println("h"); return 3 }
func main() {
var (
a int = f()
b int = g()
c int = h()
)
fmt.Println(a, b, c)
}

outputs:

f
g
h
1 2 3

Aforementioned “normally” means that it’s done inside function. When it goes to top level declarations (at package level) as in program#1 situation is more interesting as initialization dependencies come into play:

package main
import "fmt"
var (
a = c — 2
b = 2
c = f()
)
func f() int {
fmt.Printf("inside f and b = %d\n", b)
return b + 1
}
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}

Declaration order is as follows:

  • b is first since it doesn’t have any dependencies on uninitialized variables,
  • c is next as after first initialization cycle b is already declared (which is required by f function),
  • a is handled in 3rd cycle when c is ready

Program’s output for formalities:

inside f and b = 2
1
2
3

Each initialization cycle picks first (in declaration order) variable which is ready . The whole procedure last till all variables are done or compiler finds a loop like in:

package main
import "fmt"
var (
a = b
b = c
c = f()
)
func f() int {
return a
}
func main() {
fmt.Println(a, b, c)
}

which produces compilation-time error “initialization loop”.

Initialization dependency machinery work at package level:

sandbox.go

package main
import "fmt"
var (
a = c — 2
b = 2
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}

utils.go

package main
var c = f()
func f() int {
return b + 1
}

builds glibly and outputs:

1
2
3

If you liked what is above please boost work on future stories by following me.