chapter2: How you can get duplicated go.itab interface definitions
siebenmann opened this issue · comments
You asked why go.itabs are dupok. I believe the fundamental reason is that Go can be statically creating iface structures outside of either the package that defines the type or the package that defines the interface.
To make this concrete, here's a scenario where I believe that Go has to create duplicate ones. Suppose that we have four packages, A, B, C, D, and a main package. A defines a concrete type T, B defines an interface I (and perhaps some functions that take it), and both C and D import A and B and statically create B.I instances from A.T instances. Now we use both C and D in our main program.
Package A and B don't know about each other, so neither can define the iface<B.I, A.T> structure. Since C and D may be used by themselves, each must define this separately; they both need it and they have no guaranteed source of it outside themselves. Then when we use both of them together in our main program, we have duplicate definitions of iface<B.I, A.T>, one from each package, so Go must make such duplicates harmless.
That makes perfect sense @siebenmann, thanks for the great explanation.
I've added an exact reproduction of what you've described here.
Layout:
$ tree
.
├── A
│ └── lib.go
├── B
│ └── lib.go
├── C
│ └── lib.go
├── D
│ └── lib.go
└── main.go
As you've described:
A
declares a typeCalc
which implementsB.Adder
.
package A
type Calc struct{}
func (c *Calc) Add(a, b int32) int32 { return a + b }
B
declares anAdder
interface.
package B
type Adder interface{ Add(a, b int32) int32 }
C
implements a functionAdd
that instantiates aniface<B.Adder, *A.Calc>
internally:
package C
import (
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/A"
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/B"
)
func Add(a, b int32) int32 {
var adder B.Adder = &A.Calc{}
return adder.Add(a, b)
}
D
does the same thing asC
package D
import (
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/A"
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/B"
)
func Add(a, b int32) int32 {
var adder B.Adder = &A.Calc{}
return adder.Add(a, b)
}
- Finally, the
main package
callsC.Add
andD.Add
:
package main
import (
"fmt"
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/C"
"github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/D"
)
func main() {
fmt.Println(C.Add(10, 32))
fmt.Println(D.Add(10, 32))
}
Looking at the output of go build
:
$ go build -x
# ...
packagefile github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/C=$HOME/.cache/go-build/ff/ffc441d2cc7fd2bd9e12722f11fd3407dc4280577c0c74f8cb5241d72792d554-d
packagefile github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/D=$HOME/.cache/go-build/7c/7cc79061754b8fa877646bcbf5f10814217ac3dc48fc66c5964f08dd823695de-d
# ...
We can see the linker importing the archives for both package C
& D
, as expected.
Now if we look for itabs
in those archives:
$ go tool nm $HOME/.cache/go-build/ff/ffc441d2cc7fd2bd9e12722f11fd3407dc4280577c0c74f8cb5241d72792d554-d | grep 'itab\.'
af7 R go.itab.*github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/A.Calc,github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/B.Adder
$ go tool nm $HOME/.cache/go-build/7c/7cc79061754b8fa877646bcbf5f10814217ac3dc48fc66c5964f08dd823695de-d | grep 'itab\.'
af7 R go.itab.*github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/A.Calc,github.com/teh-cmc/go-internals/chapter2_interfaces/issue_7/B.Adder
We see that the itab
for iface<B.Adder, *A.Calc>
is indeed duplicated in both archives; so the linker will have to pick one.
I've added a link to this discussion in the relevant part of chapter 2; and will be closing this now.
Don't hesitate to add more comments even if it's closed!
Hopefully I'll take the time to update chapter 2 with all these learnings some day.