Go泛型和接口

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

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)
}

huangapple
  • 本文由 发表于 2023年6月21日 21:55:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76524086.html
匿名

发表评论

匿名网友

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

确定