Go version
go 1.22.0
Output of go env
in your module/workspace:
$go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/Users/tonybai/Library/Caches/go-build'
GOENV='/Users/tonybai/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/tonybai/Go/pkg/mod'
GONOPROXY='bitbucket.org/bigwhite/t'
GOOS='darwin'
GOPATH='/Users/tonybai/Go'
GOPROXY='https://goproxy.cn'
GOROOT='/Users/tonybai/.bin/go1.22.0'
GOSUMDB='off'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/tonybai/.bin/go1.22.0/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.22.0'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1615674647=/tmp/go-build -gno-record-gcc-switches -fno-common'
What did you do?
I have example below:
// main.go
package main
import (
"fmt"
)
var (
v0 = constInitCheck()
v1 = variableInit("v1")
v2 = variableInit("v2")
)
const (
c1 = "c1"
c2 = "c2"
)
func constInitCheck() string {
if c1 != "" {
fmt.Println("main: const c1 has been initialized")
}
if c1 != "" {
fmt.Println("main: const c2 has been initialized")
}
return ""
}
func variableInit(name string) string {
fmt.Printf("main: var %s has been initialized\n", name)
return name
}
func init() {
fmt.Println("main: first init func invoked")
}
func init() {
fmt.Println("main: second init func invoked")
}
func main() {
// do nothing
}
What did you see happen?
Compile and run the code in go 1.22.0,I got this output:
$go run main.go
main: var v1 has been initialized
main: var v2 has been initialized
main: const c1 has been initialized
main: const c2 has been initialized
main: first init func invoked
main: second init func invoked
What did you expect to see?
According to the latest go spec:
Within a package, package-level variable initialization proceeds stepwise, with each step selecting the variable earliest in declaration order which has no dependencies on uninitialized variables.
More precisely, a package-level variable is considered ready for initialization if it is not yet initialized and either has no [initialization expression](https://go.dev/ref/spec#Variable_declarations) or its initialization expression has no dependencies on uninitialized variables. Initialization proceeds by repeatedly initializing the next package-level variable that is earliest in declaration order and ready for initialization, until there are no variables ready for initialization.
v0、v1 and v2 all have initializaiton expression , and should be considered as "not ready for initialization"。their init order should be v0 -> v1 -> v2。but the real init order is v1 -> v2 -> v0。
Comment From: bigwhite
If we place const declaration block before var declaration block, the init order will become v0 -> v1 -> v2:
$go run main.go // go1.22.0
main: const c1 has been initialized
main: const c2 has been initialized
main: var v1 has been initialized
main: var v2 has been initialized
main: first init func invoked
main: second init func invoked
Comment From: seankhliao
constInitCheck() has a dependency on c1 and c2, which are uninitialized at that point. variableInit has no dependency. this all appears consistent with the current spec.
Comment From: bigwhite
@seankhliao thanks for instant reply. Go spec says "no dependencies on uninitialized variables" , but c1 and c2 are uninitialized constants. there is a little bit of inconsistency.
Comment From: randall77
This looks like it changed between 1.21 and 1.22. I agree with the OP, constants are not variables. @gri
Comment From: randall77
@griesemer
Comment From: gopherbot
Change https://go.dev/cl/575075 mentions this issue: cmd/compile: put constants before variables in initialization order
Comment From: randall77
Bisect points to https://go-review.googlesource.com/c/go/+/517617, switching to use types2 for init order, which makes sense. types2 always had this bug. FYI @mdempsky
Comment From: zigo101
[edit]: BTW, should this be backported? As it may cause some wrong initial values: https://go.dev/play/p/2HA4DR3_cjD
Just a curious BTW question: why is a
initialized after b
?
By my understanding of the spec, the order should be inverted.
package main
var x = 0
var a = foo()
var b = x
func foo() int {
x++
return x
}
func main() {
println(a, b) // 1 0
}
Comment From: ianlancetaylor
@go101 Thanks. Would you mind opening a separate issue for that? The gc compiler prints 1, 0
while gccgo prints 1, 1
.
Comment From: randall77
Reopening for @griesemer to decide if anything needs to change in the spec.
@go101 I don't think this issue warrants backporting. The workaround is pretty easy.
Comment From: griesemer
@go101 Re: your question about this code:
package main
var x = 0
var a = foo()
var b = x
func foo() int {
x++
return x
}
func main() {
println(a, b) // 1 1 for Go 1.23
}
The result should be 1 1: x
is initialized first as it doesn't depend on any other variables and thus is ready for initialization and it's first in the source. Then a
is initialized: it depends on f
which depends on x
but x
is initialized, so f
can proceed and the result is 1 for a
. Then b
is set to the value of a
which is 1. This matches gccgo
.
With respect to the original issue, constants are never "initialized", they have their values already at compile time. The bug was fixed with CL 575075.
(Technical aside: constants appear in the implementation code determining initialization order only because that code is also used to detect invalid cycles among constant declarations; constants don't have an impact on initialization order. The bug was that constants were treated like variables w/o dependencies and thus their source order influenced the initialization order. The fix was to prioritize a constant always before any variable which effectively removes them from the initialization order - they are "pre-initialized" if you will.)
With respect to the spec: I don't think anything needs to change. The spec explicitly talks about variables, not constants in this context.
With respect to backporting: I think we should backport this. It's easy enough and it is a bug with respect to the spec.
Closing this issue as fixed but will create a backport issue.
Comment From: griesemer
@gopherbot please consider this for backport to 1.22, it's a bug with respect to the spec.
Comment From: gopherbot
Backport issue(s) opened: #67820 (for 1.22).
Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://go.dev/wiki/MinorReleases.