Go – storing structs with the same embedded struct in a list

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

Go - storing structs with the same embedded struct in a list

问题

我有多个具有相同嵌入结构的结构体。在其中一个结构体中,我想存储任何嵌入基本结构的结构体的列表。这里是一个小片段展示了这种情况。

package main

type Symbol interface{}

type Base struct {
	a int32
}

type Foo struct {
	Base
	b int32
	Symbols []Base
	// 下面的代码可以工作
	// Symbols []Symbol
}

type Bar struct {
	Base
	c int32
}

func main () {
	var bar Bar
	var foo Foo
	foo.Symbols = append(foo.Symbols, bar)
}

然而,这样做是不起作用的。我得到了./main.go:25:22: cannot use bar (type Bar) as type Base in append的错误。当我使用空的Symbol接口时,一切都正常工作。然而,这种方法完全绕过了类型系统,因为现在可以将任何东西存储在列表中。我希望以某种方式表示只有BaseFooBar可以存储在列表中,这样编译器可以检查是否满足此要求。我的结构体没有任何方法,肯定没有共享行为并且可以添加到接口中的方法。向接口添加一些虚拟方法和虚拟实现似乎非常不自然。在处理这种情况的Go语言惯用方式是什么?

英文:

I have multiple structs with the same embedded struct. In one of the structs I would like to store a list of any struct that embeds the base struct. Here is a small snippet showing the case.

package main

type Symbol interface{}

type Base struct {
	a int32
}

type Foo struct {
	Base
	b int32
	Symbols []Base
	// Below works
	// Symbols []Symbol
}

type Bar struct {
	Base
	c int32
}

func main () {
	var bar Bar
	var foo Foo
	foo.Symbols = append(foo.Symbols, bar)
}

However, this does not work. I get ./main.go:25:22: cannot use bar (type Bar) as type Base in append. When I use the empty Symbol interface, everything works. However this approach completely bypasses the type system as now everything can be stored in the list. I would like to somehow denote that only Base, Foo and Bar can be stored in the list, so that compiler can check if this rquirement is met. My structs do not have any methods, for sure not the ones that share behavior and can be added to the interface. Adding some dummy method to the interface and dummy implementations seems very artificial. What is the Go idiomatic way of handling such scenarios?

答案1

得分: 1

重要的是相同的接口:

package main

import (
	"fmt"
)

type Item interface{
	fmt.Stringer
}

type ItemA struct {
}

func (a ItemA) String() string {
	return "物品A"
}

type ItemB struct {
}

func (a ItemB) String() string {
	return "物品B"
}

func main() {
	items := make([]Item, 0)
	items = append(items, ItemA{}, ItemB{})
	fmt.Printf("物品: %v", items)
}

英文:

What matters is the same interface:

package main

import (
	"fmt"
)

type Item interface{
	fmt.Stringer
}

type ItemA struct {
}

func (a ItemA) String() string {
	return "item A"
}

type ItemB struct {
}

func (a ItemB) String() string {
	return "item B"
}

func main() {
	items := make([]Item, 0)
	items = append(items, ItemA{}, ItemB{})
	fmt.Printf("Items: %v", items)
}

答案2

得分: 1

你似乎期望的是"子类型多态性"。Go语言不支持非接口类型的动态绑定。因此,你可以使用接口来实现:

package main

func main() {
    var bar Bar
    var foo Foo
    foo.Symbols = append(foo.Symbols, bar)
}

type Symbol interface {
    Symbol()
}

type Base struct {
    a int32
}

func (b Base) Symbol() {}

type Foo struct {
    Base
    b       int32
    Symbols []Symbol
}

type Bar struct {
    Base
    c int32
}

或者,如果你不喜欢使用接口,可以使用反射的技巧,像下面这样:

package main

import "fmt"
import "reflect"

func main() {
    var bar Bar
    var foo Foo
    err := foo.AddSymbol(bar)
    if err != nil {
        fmt.Println(err)
    }
}

type Base struct {
    a int32
}

func (b Base) Symbol() {}

type Foo struct {
    Base
    b      int32
    symbol []interface{} // 将字段设为私有
}

// AddSymbol: 辅助函数用于追加值
func (f *Foo) AddSymbol(in interface{}) (err error) {
    if f.symbol == nil {
        f.symbol = make([]interface{}, 0)
    }

    switch typ := in.(type) {
    case Base, Bar, Foo:
        f.symbol = append(f.symbol, in)
    default:
        return fmt.Errorf("提供的类型:%s 不受支持", typ)
    }

    return nil
}

type Bar struct {
    Base
    c int32
}
英文:

What you seem to be expecting is subtype polymorphism. Go does not support dynamic binding for non-interface types. Therefore you you could either use interfaces

package main

func main(){
	var bar Bar
	var foo Foo
	foo.Symbols = append(foo.Symbols, bar)
}

type Symbol interface {
	Symbol()
}

type Base struct {
	a int32
}

func (b Base)Symbol(){}

type Foo struct {
	Base
	b int32
	Symbols []Symbol
}

type Bar struct {
	Base
	c int32
}

or if you dislike using interfaces, you can use a trick like below with reflect.

package main

import "fmt"

func main(){
	var bar Bar
	var foo Foo
	err := foo.AddSymbol(bar)
	if err != nil{
		fmt.Println(err)
	}
}

type Base struct {
	a int32
}

func (b Base)Symbol(){}

type Foo struct {
	Base
	b int32
	symbol []interface{} // field made private
}

// AddSymbol : helper to append values
func (f *Foo)AddSymbol(in interface{})(err error){
	if f.symbol == nil{
		f.symbol = make([]interface{},0)
	}

	switch typ := in.(type) {
	case Base,Bar,Foo:
		f.symbol = append(f.symbol,in)
	default:
		return fmt.Errorf("provided type: %s is not supported",typ)
	}

	return nil
}

type Bar struct {
	Base
	c int32
}

答案3

得分: 1

我进行了一些搜索和阅读。我想要实现的目标需要所谓的“和类型”。目前,Go语言不支持和类型。然而,有几种替代方法可以模拟和类型的行为。在这里Alternatives to sum types in Go中对它们进行了很好的描述。

此外,看起来和类型可能会在Go 2中得到支持。更多阅读材料请参考proposal: spec: add sum types / discriminated unionsspec: generics: use type sets to remove type keyword in constraints

英文:

I did some searching and reading. What I want to achieve requires so called "sum types". Currently Go does not support sum types. However, there are few alternative to simulate the behavior of sum types. They are quite nicely described here Alternatives to sum types in Go.

What is more, it looks like sum types will probably be supported in the Go 2. Further reading proposal: spec: add sum types / discriminated unions, spec: generics: use type sets to remove type keyword in constraints.

huangapple
  • 本文由 发表于 2021年10月4日 01:50:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/69427448.html
匿名

发表评论

匿名网友

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

确定