I'm trying to figure out how go handles type conversion between structs. Everything I have read tells me that types with the same underlying type are considered compatible and type conversion happens implicitly. If that is the case, why does the code below not work? Both Foo
and Bar
implement the FooI
interface, and they both add an x
property of type string. Yet, when I pass a struct of type Bar
to AcceptBarOrFoo
that expects a struct of type Foo
, I get a type mismatch compile error.
package main
import (
"play.ground/bar"
"play.ground/foo"
)
func main() {
AcceptBarOrFoo(bar.Bar{})
}
func AcceptBarOrFoo(foo.Foo) interface{} {
return nil
}
// -- iface/iface.go --
package iface
type FooI interface {
a() string
b(int) int
}
// -- foo/foo.go --
package foo
import (
"play.ground/iface"
)
type Foo struct {
iface.FooI
x string
}
// -- bar/bar.go --
package bar
import (
"play.ground/iface"
)
type Bar struct {
iface.FooI
x string
}
Foo's x
is different from Bar's x
because non-exported identifiers are never equal across package boundaries. Fix by exporting the fields in foo.Foo and bar.Bar:
type Foo struct {
iface.FooI
X string // <-- start with capital letter to export
}
To use a foo.Foo or bar.Bar as an argument value, a foo.Foo and bar.Bar must be assignable to the argument's type. It does not work to use foo.Foo as the argument type because named types are not assignable to each other. However, named types are assignable to unnamed types when the two types share the same underlying type. Declare the argument as an unnamed type:
func AcceptBarOrFoo(struct {
iface.FooI
X string
}) interface{} {
return nil
}
With these changes, the following code compiles:
AcceptBarOrFoo(bar.Bar{})
AcceptBarOrFoo(foo.Foo{})
Run the example on the Go playground
Another option is to use a conversion to a common type. In the following code, foo.Foo is the common type and bar.Bar is converted to a foo.Foo.
func Accept(foo.Foo) interface{} {
return nil
}
...
Accept(foo.Foo{})
Accept(foo.Foo(bar.Bar{}))
Run the example on the Go playground.
Note: foo.Foo and bar.Bar must have the same fields for the above to work (field names are exported, fields in same order, fields have same types).
Some notes about Go: