使用泛型时,从接口中获取基本类型

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

Getting a base type from interface when using generics

问题

我有一个无法更改的函数,函数的形式是foo(interface{})。在其他一些类型中,这个函数可以接受[]byte类型,但不能接受[16]byte类型。我想编写一个基于泛型的小适配器,以支持UUID,而不是编写foo(uuid[:]),但我不想陷入具体实现的细节。例如,我想要的是:

type AcceptedTypes interface {
	int | ~[16]byte
}

但我不知道如何做到这一点。当我们使用特定类型时,很容易将它们转换为正确的类型。

func rewrittenFoo[T AcceptedCertainTypes](val T) {
	var t interface{} = *new(T)
	switch t.(type) {
	case gofrsuuid.UUID:
		k := val.(gofrsuuid.UUID)
		foo(k[:])
	case googleuuid.UUID:
		k := val.(googleuuid.UUID)
		foo(k[:])
	}
}

但是如何将包含gofrsuuid.UUIDinterface{}转换为基本类型[16]byte呢?

英文:

I have a function that I cannot change, the function looks like foo(interface{}). Among some other types, this function can take a type []byte but cannot take [16]byte. I want to write a little adapter based on generics that add support for UUIDs instead of writing foo(uuid[:]), but I don't want to get hung up on specific implementations. For example, instead of

import (
	gofrsuuid "github.com/gofrs/uuid"
	googleuuid "github.com/google/uuid"
)

type AcceptedCertainTypes interface {
	int | gofrsuuid.UUID | googleuuid.UUID // | etc...
}

I want to have

type AcceptedTypes interface {
	int | ~[16]byte
}

But I have no idea how to do this. When we use certain types, it is easy to turn them into the right ones.

func rewrittenFoo[T AcceptedCertainTypes](val T) {
	var t interface{} = *new(T)
	switch t.(type) {
	case gofrsuuid.UUID:
		k := val.(gofrsuuid.UUID)
		foo(k[:])
	case googleuuid.UUID:
		k := val.(googleuuid.UUID)
		foo(k[:])
	}
}

But how to convert interface{} that contains gofrsuuid.UUID to that base type [16]byte?

答案1

得分: 1

你无法对类似~[16]byte这样的联合类型的近似项进行详尽的类型切换,因为根据定义,该类型集是无界的。你需要使用反射来提取数组类型,并最终对其进行切片。

只有一个近似项

如果近似项~[16]byte是联合类型中唯一的项,你可以在default块中进行类型切换并处理它。这是基于类型参数的编译时类型安全性,因此default块不会处理任何意外的类型:

func rewrittenFoo[T int | ~[16]byte](val T) {
    switch t := any(val).(type) {
    // 首先处理所有非近似类型的情况
    case int:
        foo(t) // t 是 int

    // 这将处理 T 类型集中除 int 之外的所有其他类型
    // 因此实际上是 ~[16]byte
    default:
        v := reflect.ValueOf(t).Convert(reflect.TypeOf([16]byte{})).Interface().([16]byte)
        foo(v[:])
    }
}

Playground: https://go.dev/play/p/_uxmWGyEW5N

多个不同的近似项

如果联合类型中有多个波浪线项,你不能依赖于default情况。如果底层类型都不同,你可以根据reflect.Kind进行切换:

func rewrittenFoo[T int | ~float64 | ~[16]byte](val T) {
    // 首先处理所有非近似类型的情况
    switch t := any(val).(type) {
    case int:
        foo(t)
    }

    switch reflect.TypeOf(val).Kind() {
    case reflect.Float:
        // ...

    case reflect.Array:
        // ...
    }
}

多个相似的近似项

类型参数对此帮助不大,只需使用any并对所有可能的类型进行详尽的类型切换。你可以将具有相同底层类型的类型分组,并像上面示例中使用Value#Convert或类型特定的方法(如Value#Int()Value#String())来处理它们。

英文:

You can't have an exhaustive type switch on a union's approximate term like ~[16]byte, because the type set by definition is unbound. You have to use reflection to extract the array type and eventually reslice it.

Only one approximate term

If the approximate term ~[16]byte is the only one in the union, you can type-switch and handle it in the default block. This is based on the compile-time type safety of type parameters, so that default block will not run with any unexpected type:

func rewrittenFoo[T int | ~[16]byte](val T) {
    switch t := any(val).(type) {
    // handle all non-approximate type cases first
    case int:
        foo(t) // t is int

    // this will be all other types in T's type set that are not int
    // so effectively ~[16]byte
    default:
        v := reflect.ValueOf(t).Convert(reflect.TypeOf([16]byte{})).Interface().([16]byte)
        foo(v[:])
    }
}

Playground: https://go.dev/play/p/_uxmWGyEW5N

Many different approximate terms

If you have many tilde terms in a union, you can't rely on default case. If the underlying types are all different, you may be able to switch on reflect.Kind:

func rewrittenFoo[T int | ~float64 | ~[16]byte](val T) {
    // handle all non-approximate type cases first
    switch t := any(val).(type) {
    case int:
        foo(t)
    }

    switch reflect.TypeOf(val).Kind() {
    case reflect.Float:
        // ...

    case reflect.Array:
        // ...
    }
}

Many similar approximate terms

Type parameters won't help much, just use any and exhaustively type-switch an all possible types. You can group types that you know have the same underlying type and use Value#Convert as shown above — or type-specific methods like Value#Int() or Value#String() —, to handle them similarly.

huangapple
  • 本文由 发表于 2022年6月2日 10:11:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/72469902.html
匿名

发表评论

匿名网友

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

确定