Search code examples
gostructinterfacecasting

Golang struct type conversion


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
}

Solution

  • 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:

    • There are conversions from one concrete type to another.
    • Go is famous for having no implicit conversions in expressions, but there are implicit conversions in some assignment scenarios.