Golang结构体类型转换

huangapple go评论65阅读模式
英文:

Golang struct type conversion

问题

我正在尝试弄清楚Go语言如何处理结构体之间的类型转换。我所读到的所有内容都告诉我,具有相同底层类型的类型被认为是兼容的,并且类型转换是隐式进行的。如果是这样的话,为什么下面的代码不起作用呢?FooBar都实现了FooI接口,并且它们都添加了一个类型为字符串的x属性。然而,当我将类型为Bar的结构体传递给期望类型为FooAcceptBarOrFoo函数时,我得到了一个类型不匹配的编译错误。

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
}
英文:

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
}

答案1

得分: 3

Foo的x与Bar的x不同,因为非导出标识符在包边界上永远不相等。通过导出 foo.Foo和bar.Bar中的字段来修复:

type Foo struct {
    iface.FooI
    X string // <--- 以大写字母开头以导出
}

要将foo.Foo或bar.Bar用作参数值,foo.Foo和bar.Bar必须可赋值给参数的类型。使用foo.Foo作为参数类型是不起作用的,因为命名类型不能相互赋值。然而,当两个类型共享相同的底层类型时,命名类型可以赋值给未命名类型。将参数声明为未命名类型:

func AcceptBarOrFoo(struct {
    iface.FooI
    X string
}) interface{} {
    return nil
}

通过这些更改,以下代码可以编译:

AcceptBarOrFoo(bar.Bar{})
AcceptBarOrFoo(foo.Foo{})

在Go playground上运行示例:点击这里

另一种选择是使用转换为公共类型。在下面的代码中,foo.Foo是公共类型,bar.Bar被转换为foo.Foo。

func Accept(foo.Foo) interface{} {
    return nil
}

...

Accept(foo.Foo{})
Accept(foo.Foo(bar.Bar{}))

在Go playground上运行示例:点击这里

注意:foo.Foo和bar.Bar必须具有相同的字段才能使上述代码工作(字段名称已导出,字段顺序相同,字段类型相同)。


关于Go的一些说明:

  • 可以从一个具体类型转换为另一个类型。
  • Go因在表达式中没有隐式转换而闻名,但在某些赋值场景中存在隐式转换。
英文:

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 // &lt;-- 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.

答案2

得分: 1

你不能将一个具体类型转换为另一个具体类型,它们并不相同。在Go语言中,无法定义这种类型的自动转换方式。最多,你可以定义一个函数,接受一个Bar类型的参数,并构建并返回一个字段值与输入的Bar相同的新的Foo类型对象。

我所阅读的所有内容都告诉我,如果底层类型相同,高阶类型被认为是兼容的,并且类型转换会隐式发生。

不清楚你的信息来源是什么,但是没有任何东西会声明或暗示这一点,这是不正确的。Go语言不会进行任何隐式转换,这是Go语言的一个重要特性。给定type Foo struct { a int }type Bar struct { a int },你永远无法将Bar类型的对象赋值给Foo类型的变量。

当类型满足接口时,你可以从任一具体类型转换为接口类型。你的AcceptBarOrFoo方法应该接受一个接口类型(FooBar都满足该接口),而不是具体类型。鉴于接口只定义方法(而不是成员),并且FooBar都没有任何方法,你的接口应该是空接口interface{}。传入的值除了稍后将其转换回具体类型以访问其成员之外,没有其他用途,但这并不是接口的主要用途。

英文:

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.

huangapple
  • 本文由 发表于 2022年6月22日 02:43:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/72705723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定