Home > other >  Golang struct type conversion
Golang struct type conversion

Time:06-22

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.

Go Playground

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
}

CodePudding user response:

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).

CodePudding user response:

You cannot convert one concrete type to another concrete type, ever. They are not the same. There is no way to define this type of automatic casting in Go. At best, you could define a function that accepts a Bar and builds and returns a new Foo with its fields set to the same values as the input Bar.

Everything I have read tells me that if the underlying types are the same, the higher order types are considered compatible and type conversion happens implicitly

It's unclear what your source for this is, but nothing would have ever stated or implied this, it's simply not true. Go does no implicit conversion, of anything. That's a big, loudly advertised feature of Go. Given type Foo struct { a int } and type Bar struct { a int }, you can never assign an object of type Bar to a variable of type Foo.

You can convert from either concrete type to an interface type, when the type satisfies the interface. Your AcceptBarOrFoo method should accept an interface type (which both Foo and Bar satisfy), not a concrete type. Given that interfaces only define methods (not members), and given neither Foo or Bar have any methods, your interface would be the empty interface, interface{}. The value passed in would serve no purpose, except for you to later convert it back to a concrete type to access its members, but that's not really what interfaces are for.

  • Related