Go version
go version go1.23.0 linux/amd64
Output of go env in your module/workspace:
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/leabit/.cache/go-build'
GOENV='/home/leabit/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/leabit/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/leabit/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/leabit/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/leabit/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/leabit/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/leabit/goprojects/exeexe/go.mod'
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 -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1849554432=/tmp/go-build -gno-record-gcc-switches'
What did you do?
Golang specification for type identity states the following for the struct types:
Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. Non-exported field names from different packages are always different.
Thus, it has 4 requirements:
- same sequence of fields
- same corresponding names
- identical types of corresponding fields
- identical tags of corresponding fields
However, there are cases related to embedding aliases of unnamed types where, despite meeting all four previous requirements, the compiler still fails to compile.
I will explain the problem through few examples. The following notes holds for all examples:
- three packages are used:
pac1, pac2 and main
- declared alias types in
pac and pac2 are unnamed types struct{} (thus, they are identical)
- struct tags aren't used, so they are ignored
(1) This example successfully compiles (as expected). M1 and M2 have the same sequence of fields. Names, even thought not explicitly defined, are indirectly given by embedding and thus they are same (MyType1). Types of the fields (field MyType1) are also same (struct{}).
pac1:
package pac1
type MyType1 = struct{}
pac2:
package pac2
type MyType1 = struct{}
main:
package main
import (
"exeexe/pac1"
"exeexe/pac2"
)
type M1 struct {
pac1.MyType1
}
type M2 struct {
pac2.MyType1
}
func main() {
_ = M2(M1{})
}
(2) In this example MyType1 = struct{} is replaced with MyType2 = struct{} in pac2. Also, pac2.MyType2 is now embedded in M2 (instead of pac2.MyType1) in main package. As expected, it doesn't compile. Even thought the types of the fields are the same, the names are not (MyType1 != MyType2), thus requirement 2 doesn't hold.
pac1:
package pac1
type MyType1 = struct{}
pac2:
package pac2
type MyType2 = struct{}
main:
package main
import (
"exeexe/pac1"
"exeexe/pac2"
)
type M1 struct {
pac1.MyType1
}
type M2 struct {
pac2.MyType2
}
func main() {
_ = M2(M1{})
}
(3) This example shows the actual problem. The only difference compared to the previous example is that embedded field (pac2.MyType2) is now replaced by non-embedded (ordinary) field with the name MyType1 (type is the same). Since specification for type identity doesn't make a distinct between embedded and ordinary field in the struct this should be valid and compile (all requirements are met).
pac1:
package pac1
type MyType1 = struct{}
pac2:
package pac2
type MyType2 = struct{}
main:
package main
import (
"exeexe/pac1"
"exeexe/pac2"
)
type M1 struct {
pac1.MyType1
}
type M2 struct {
MyType1 pac2.MyType2
}
func main() {
_ = M2(M1{})
}
(4) This example just shows that even when MyType1 pac2.MyType2 is replaced with MyType1 pac1.MyType1 in M2 (so just the name is added), it still doesn't compile.
pac1:
package pac1
type MyType1 = struct{}
pac2:
package pac2
type MyType2 = struct{}
main:
package main
import (
"exeexe/pac1"
)
type M1 struct {
pac1.MyType1
}
type M2 struct {
MyType1 pac1.MyType1
}
func main() {
_ = M2(M1{})
}
What did you see happen?
Inconsistency between specification and implementation
What did you expect to see?
Since I am following specification, I expect examples (3) and (4) to compile, or to modify the specification to address the difference.
Go version
go version go1.23.0 linux/amd64
Output of
go envin your module/workspace:What did you do?
Golang specification for type identity states the following for the struct types:
Thus, it has 4 requirements:
However, there are cases related to embedding aliases of unnamed types where, despite meeting all four previous requirements, the compiler still fails to compile.
I will explain the problem through few examples. The following notes holds for all examples:
pac1,pac2andmainpacandpac2are unnamed typesstruct{}(thus, they are identical)(1) This example successfully compiles (as expected).
M1andM2have the same sequence of fields. Names, even thought not explicitly defined, are indirectly given by embedding and thus they are same (MyType1). Types of the fields (fieldMyType1) are also same (struct{}).pac1:pac2:main:(2) In this example
MyType1 = struct{}is replaced withMyType2 = struct{}inpac2. Also,pac2.MyType2is now embedded inM2(instead ofpac2.MyType1) inmainpackage. As expected, it doesn't compile. Even thought the types of the fields are the same, the names are not (MyType1!=MyType2), thus requirement 2 doesn't hold.pac1:pac2:main:(3) This example shows the actual problem. The only difference compared to the previous example is that embedded field (
pac2.MyType2) is now replaced by non-embedded (ordinary) field with the nameMyType1(type is the same). Since specification for type identity doesn't make a distinct between embedded and ordinary field in the struct this should be valid and compile (all requirements are met).pac1:pac2:main:(4) This example just shows that even when
MyType1 pac2.MyType2is replaced withMyType1 pac1.MyType1inM2(so just the name is added), it still doesn't compile.pac1:pac2:main:What did you see happen?
Inconsistency between specification and implementation
What did you expect to see?
Since I am following specification, I expect examples (3) and (4) to compile, or to modify the specification to address the difference.