英文:
Go Generics and Interfaces
问题
我正在努力理解泛型和接口如何相互作用,并且能够确定从定义为返回接口的函数中返回哪个实现。
我该如何使下面的代码按照我想要的方式工作?
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type IntegrationConfig[T interface{}] interface {
}
type NamedJsonSettings[T interface{}] json.RawMessage
func (m *NamedJsonSettings[T]) GetConfig() (T, error) {
conf := new(T)
err := json.Unmarshal(*m, conf)
return *conf, err
}
type NamedConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
func getConfig[T interface{}]() IntegrationConfig[T] {
serverConfigStr := `{
"username": "myusername",
"password": "supersecret"
}`
sc := NamedJsonSettings[T](serverConfigStr)
c, err := sc.GetConfig()
if err != nil {
fmt.Printf("ERROR: %s", err)
}
return c
}
func main() {
c := getConfig[NamedConfig]()
fmt.Printf("%+v\n", c)
fmt.Printf("%s\n", c.Username)
fmt.Printf("%s\n", c.Password)
fmt.Printf("%s\n", reflect.TypeOf(c).String())
}
运行代码会生成以下错误:
./scratch_1.go:43:23: c.Username undefined (type IntegrationConfig[NamedConfig] has no field or method Username)
./scratch_1.go:44:23: c.Password undefined (type IntegrationConfig[NamedConfig] has no field or method Password)
如果我删除这两行,当我在控制台中运行它时,会看到以下输出,这是我期望的结果:
{Username:myusername Password:supersecret}
main.NamedConfig
我在哪里出错了?此外,如果有一些关于更好地理解泛型的资源链接,特别是在处理接口时的情况,我将非常感激。
英文:
I'm struggling understanding how generics and interfaces interact with each over and to have the ability to say which implementation will be returned from a function that is defined to return an interface.
How can I get the following code to work the way I want.
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type IntegrationConfig[T interface{}] interface {
}
type NamedJsonSettings[T interface{}] json.RawMessage
func (m *NamedJsonSettings[T]) GetConfig() (T, error) {
conf := new(T)
err := json.Unmarshal(*m, conf)
return *conf, err
}
type NamedConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
func getConfig[T interface{}]() IntegrationConfig[T] {
serverConfigStr := `{
"username": "myusername",
"password": "supersecret"
}`
sc := NamedJsonSettings[T](serverConfigStr)
c, err := sc.GetConfig()
if err != nil {
fmt.Printf("ERROR: %s", err)
}
return c
}
func main() {
c := getConfig[NamedConfig]()
fmt.Printf("%+v\n", c)
fmt.Printf("%s\n", c.Username)
fmt.Printf("%s\n", c.Password)
fmt.Printf("%s\n",reflect.TypeOf(c).String())
}
Running this generates the following error:
./scratch_1.go:43:23: c.Username undefined (type IntegrationConfig[NamedConfig] has no field or method Username)
./scratch_1.go:44:23: c.Password undefined (type IntegrationConfig[NamedConfig] has no field or method Password)
If I remove those two lines, I see the following when I run it in the console, which is what I would expect:
{Username:myusername Password:supersecret}
main.NamedConfig
Where am I going wrong here? Also, any links to some good resources in understanding generics better, in particular in the case when working with interfaces in a way like this, would be greatly appreciated.
答案1
得分: 1
函数getConfig[NamedConfig]()
返回IntegrationConfig[NamedConfig]
,我们可以看到其定义如下:
type IntegrationConfig[T interface{}] interface {
}
可以看出,这只是一个interface{}
(也称为any
),并且没有字段。任何值都可以转换为any
,因此实际返回的对象具有字段是无关紧要的。我们可以构造一个类似的测试案例:
package main
import "fmt"
type t struct {
field int
}
func a() any {
return t{field: 12}
}
func main() {
value := a()
fmt.Printf("value.field = %d\n", value.field)
}
尽管存储在value
中的实际值具有名为field
的字段,但类型any
不会公开该字段。
如果要访问字段或方法,这些字段/方法必须由变量的类型公开,而不管字段/方法是否由值的类型公开。(这与Python或JavaScript不同,后者允许您访问对象上的任何字段或方法,只要该字段或方法在运行时存在。)
如何修复
首先,使用any
而不是interface{}
是正常的。它们的含义相同,但any
更为常见。
各种中间类型是不必要的。您可以直接从泛型中返回T
。由于T
是泛型内部的普通类型,因此不必使用new(T)
。
在此代码中,变量c
的类型为NamedConfig
,而不是IntegrationConfig[NamedConfig]
(只是any
)。由于它具有类型NamedConfig
- 由于变量本身具有该类型而不仅仅是值 - 您可以访问字段。
package main
import (
"encoding/json"
"fmt"
)
type NamedConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
func getConfig[T any]() T {
serverConfigStr := `{
"username": "myusername",
"password": "supersecret"
}`
var value T
if err := json.Unmarshal([]byte(serverConfigStr), &value); err != nil {
fmt.Printf("ERROR: %v\n", err)
}
return value
}
func main() {
// 在这里,c 的类型为 NamedConfig。
c := getConfig[NamedConfig]()
fmt.Printf("%+v\n", c)
fmt.Printf("%s\n", c.Username)
fmt.Printf("%s\n", c.Password)
}
英文:
The function getConfig[NamedConfig]()
returns IntegrationConfig[NamedConfig]
, and we can see the definition for that:
type IntegrationConfig[T interface{}] interface {
}
You can see that this is just interface{}
(a.k.a. any
) and it has no fields. Any value can be converted to any
, so the fact that the actual object returned has fields is irrelevant. We can construct a similar test case like this:
package main
import "fmt"
type t struct {
field int
}
func a() any {
return t{field: 12}
}
func main() {
value := a()
fmt.Printf("value.field = %d\n", value.field)
}
Even though the actual value stored in value
has a field named field
, the type any
does not expose that field.
If you want to access fields or methods, those fields / methods must be exposed by the variable’s type, and it does not matter if the fields / methods are exposed by the value’s type. (This is different from, say, Python or JavaScript, which allow you to access any field or method on an object, as long as that field or method exists at runtime.)
How To Fix
First of all, it is normal to use any
instead of interface{}
. They mean the same thing, but any
is more typical.
The various intermediate types are not necessary. You can directly return T
from your generics. Since T
is an ordinary type inside your generics, you do not have to use new(T)
.
In this code, the variable c
has type NamedConfig
, rather than IntegrationConfig[NamedConfig]
(which is just any
). Since it has the type NamedConfig
—since the variable itself has that type and not just the value—you can access the fields.
package main
import (
"encoding/json"
"fmt"
)
type NamedConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
func getConfig[T any]() T {
serverConfigStr := `{
"username": "myusername",
"password": "supersecret"
}`
var value T
if err := json.Unmarshal([]byte(serverConfigStr), &value); err != nil {
fmt.Printf("ERROR: %v\n", err)
}
return value
}
func main() {
// Here, c has type NamedConfig.
c := getConfig[NamedConfig]()
fmt.Printf("%+v\n", c)
fmt.Printf("%s\n", c.Username)
fmt.Printf("%s\n", c.Password)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论