Go using generic slice field in struct

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

Go using generic slice field in struct

问题

我们想要有以下代码:

type ResponseListDataPayload struct {
	List     []*interface{} `json:"list"` //generic
	TotalCnt int64          `json:"totalCnt"`
	Page     int64          `json:"page"`
	Step     int64          `json:"step"`
}

并且List可以接受[]*model.SomeModel{}

queryResults := []*model.SomeModel{}
resposeResult := &ResponseListDataPayload{
	List:     queryResults,
	TotalCnt: cnt,
	Page:     pageInt,
	Step:     stepInt,
}

或者[]*model.AnotherModel{}

queryResults := []*model.AnotherModel{}
resposeResult := &ResponseListDataPayload{
	List:     queryResults,
	TotalCnt: cnt,
	Page:     pageInt,
	Step:     stepInt,
}

这在Java中非常简单,那在Go语言中是否可能实现呢?

英文:

We want to have

type ResponseListDataPayload struct {
	List     []*interface{} `json:"list"` //generic
	TotalCnt int64          `json:"totalCnt"`
	Page     int64          `json:"page"`
	Step     int64          `json:"step"`
}

and List could would accept []*model.SomeModel{}

queryResults := []*model.SomeModel{}
resposeResult := &ResponseListDataPayload{
	List:     queryResults,
	TotalCnt: cnt,
	Page:     pageInt,
	Step:     stepInt,
}

or []*model.AnotherModel{}

queryResults := []*model.AnotherModel{}
resposeResult := &ResponseListDataPayload{
	List:     queryResults,
	TotalCnt: cnt,
	Page:     pageInt,
	Step:     stepInt,
}

That's pretty straightforward in Java, could that be possible in go?

答案1

得分: 12

Go 1.18

现在你可以使用参数化结构体:

type ResponseListDataPayload[T any] struct {
    List     []T            `json:"list"` //generic
    TotalCnt int64          `json:"totalCnt"`
    Page     int64          `json:"page"`
    Step     int64          `json:"step"`
}

请记住,泛型结构体必须使用显式类型参数进行实例化:

queryResults := []*model.SomeModel{}
responseResult := &ResponseListDataPayload[*model.SomeModel]{
    List:     queryResults,
    // other fields
}

如果你想进一步提高代码重用性,并且使结构体的初始化也"泛型化",你可以使用构造函数。函数可以利用类型推断省略类型参数的编写:

// add to func signature other arguments as needed
func NewResponseFor[T any](list []T) *ResponseListDataPayload[T] {
    return &ResponseListDataPayload[T]{ List: list }
}

然后使用它:

queryResults := // some query results
responseResult := NewResponseFor(queryResults)

示例:https://gotipplay.golang.org/p/jYTHegaeubR


Go 1.17及更早版本

> 这在Go中可能吗?

不,interface{}实际上不是一个泛型类型,它只是一个具有空方法集的接口。

从形式上讲,你可以将任何具体值赋给它,因为可赋值性要求值的方法集是接口方法集的超集,而任何集合都是空集()的超集。

这与Java的List<T>之类的参数化类型不同,因此在Go中,[]Foo不能赋值给[]interface{}

你必须在循环中处理单个元素:

var m []*model.anotherModel
// populate m
for _, v := range m {
    resp.List = append(resp.List, v)
}

类似地,不要使用指向空接口的指针*interface{},而是使用interface{}

如果你的目标只是为了序列化JSON(基于结构体上的标签的存在),你可以将List字段声明为interface{},然后你可以将任何一个切片值赋给它,原因如上所述。这样可以避免额外的切片操作。然后,json包将根据interface{}中封装的具体值进行序列化。

英文:

Go 1.18

You can now have parametrized structs:

type ResponseListDataPayload[T any] struct {
    List     []T            `json:&quot;list&quot;` //generic
    TotalCnt int64          `json:&quot;totalCnt&quot;`
    Page     int64          `json:&quot;page&quot;`
    Step     int64          `json:&quot;step&quot;`
}

Remember that generic structs must be instantiated with an explicit type parameter:

queryResults := []*model.SomeModel{}
responseResult := &amp;ResponseListDataPayload[*model.SomeModel]{
    List:     queryResults,
    // other fields
}

If you want to improve code reuse even more and "genericize" also the struct initialization, you can use a constructor function. Functions can take advantage of type inference to omit writing out the type arguments:

// add to func signature other arguments as needed
func NewResponseFor[T any](list []T) *ResponseListDataPayload[T] {
    return &amp;ResponseListDataPayload[T]{ List: list }
}

and use it as:

queryResults := // some query results
responseResult := NewResponseFor(queryResults)

Example: https://gotipplay.golang.org/p/jYTHegaeubR

<hr>

Go 1.17 and below

> could that be possible in go?

No, interface{} is not actually a generic type, it's just an interface with an empty method set.

Formally, you can assign any concrete value to it because assignability requires the value's method set to be a superset of the interface method set, and any set is a superset of the empty set ().

It's not the same thing as a parametrized type like Java's List&lt;T&gt; hence in Go []Foo is not assignable to []interface{}.

You must process the single elements in a loop:

var m []*model.anotherModel
// populate m
for _, v := range m {
    resp.List = append(resp.List, v)
}

Similarly, don't use pointers to the empty interface *interface{}. Use interface{} instead.

If your goal is to just serialize JSON (based on the presence of the tags on your struct), you can declare the List field as interface{} and you’ll be able to assign either of your slice values to it, for the reason stated above. Thus avoiding an extra slice manipulation. The json package will then serialize based on the concrete values boxed in the interface{}.

答案2

得分: 2

在Go 1.18中,你将能够像这样使用真正的Go泛型:

func convert[S any](src []S) []interface{} {
	dst := make([]interface{}, 0, len(src))
	for _, v := range src {
		dst = append(dst, v)
	}
	return dst
}
//...
resp.List = convert(m)

但在1.18发布并且更多的代码库开始使用泛型之前,你仍然需要手动进行转换。

就像大家说的那样,不要使用*interface{}

英文:

In Go 1.18 you will be able to do something like, with real go generics:

func convert[S any](src []S) []interface{} {
	dst := make([]interface{}, 0, len(src))
	for _, v := range src {
		dst = append(dst, v)
	}
	return dst
}
//...
resp.List = convert(m)

But until 1.18 is out and more codebases embrace generics, you will still need to do the conversion by hand.

And like everyone said, don't use *interface{}.

huangapple
  • 本文由 发表于 2021年9月15日 20:57:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/69193609.html
匿名

发表评论

匿名网友

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

确定