英文:
Generic type which isn't of an interface type
问题
我正在尝试使用泛型来创建一个参数化类型,它可以是以下类型之一:
T、*T、T[]、map[interface{}]interface{}
其中,T
是一个可比较类型,但不是接口。
我尝试通过约束类型来表达这个概念,但由于出现了MisplacedTypeParam编译错误,导致失败:
type myType[T comparable] interface {
T | *T | T[] | map[interface{}]interface{}
}
当使用reflect
时,我也遇到了问题,获取接口的reflect.Kind
或reflect.Type
将返回接口底层的值类型,这意味着我还没有找到如何断言该类型不是接口。
基于此,我想知道表示这样一种类型的最佳替代方式是什么?
这是我的正在进行中的工作(https://github.com/mcwalrus/go-jitjson)及其主要部分:
type JitJSON[T any] struct {
data []byte
val *T
}
func (jit *JitJSON[T]) Unmarshal() (T, error) {
if jit.val != nil {
return *jit.val, nil
}
var val T
if jit.data == nil {
return val, nil
}
jit.val = &val
err := json.Unmarshal(jit.data, jit.val)
if err != nil {
return val, err
}
return *jit.val, nil
}
英文:
I'm trying to work with generics to create a parameterised type which can be of:
T, *T, T[], map[interface{}]interface{}
Where: T
is of comparable
type, but is not an interface.
I've attempted to formulate this through constrained typeset, but this fails due to MisplacedTypeParam compiler error:
type myType[T comparable] interface {
T | *T | T[] | map[interface{}]interface{}
}
I also have the issue when using reflect
, that getting the reflect.Kind
or reflect.Type
of an interface will return the value's type underlying the interface, which means I haven't figured out how to assert the type is not an interface.
From this, I am wondering what the best alternative way to represent such a type would be?
<hr>
This is my work in progress (https://github.com/mcwalrus/go-jitjson) and its main parts:
type JitJSON[T any] struct {
data []byte
val *T
}
func (jit *JitJSON[T]) Unmarshal() (T, error) {
if jit.val != nil {
return *jit.val, nil
}
var val T
if jit.data == nil {
return val, nil
}
jit.val = &val
err := json.Unmarshal(jit.data, jit.val)
if err != nil {
return val, err
}
return *jit.val, nil
}
答案1
得分: 3
很抱歉,你无法简单地将此实现为类型约束。因此,你必须依赖运行时检查。
你可以通过reflect
实现类型检查,但在你的应用程序中可能是多余的,因为标准库json
在尝试编组或解组不兼容的数据类型时已经会产生适当的错误。
如果你确实想使用reflect
来检查参数化类型的确切类型,这是一种天真但不正确的方法:
func f[T any]() {
var zero T
typ := reflect.TypeOf(zero)
fmt.Println(typ)
}
这对于大多数类型都有效,但对于接口类型来说,它将始终报告typ
为<nil>
。这是因为在Go中,接口值的赋值方式导致了这种情况。以下是正确的方法:
func f[T any]() {
var zero T
typ := reflect.TypeOf(&zero).Elem()
fmt.Println(typ)
}
这是必要的,因为在运行时,接口值只携带它们引用的具体类型的类型信息。通过获取接口变量的指针,获取指针的类型,然后获取指针类型的元素,可以提取接口变量的实际类型。
**我似乎找不到一个简明的解释为什么无法做到这一点,但以下是一些困难之处:
comparable
无法在类型联合中使用- 类型约束无法组合
- 类型约束无法递归定义
- 类型约束无法否定
- 没有与一般结构体匹配的接口
英文:
Unfortunately you cannot** implement this simply as a type constraint. Therefore, you must rely on run-time checks.
You may implement the type check through reflect
, but this may be redundant in your application, as the standard library json
already produces an appropriate error when trying to marshal or unmarshal an incompatible data type.
If you do want to use reflect
to inspect the exact type of a parameterized type, here's the naive but incorrect way to do it:
func f[T any]() {
var zero T
typ := reflect.TypeOf(zero)
fmt.Println(typ)
}
This works for most types, except for interface types, for which it will always report typ
as <nil>
. This is because of how assignment of interface values works in Go. Here is the correct way to do it:
func f[T any]() {
var zero T
typ := reflect.TypeOf(&zero).Elem()
fmt.Println(typ)
}
This is necessary because interface values, at runtime, only carry type information of the concrete type which they reference. By taking the pointer to an interface variable, taking the type of the pointer, then taking the element of the pointer type, the actual type of the interface variable can be extracted.
** I can't seem to find a concise explanation of why this can't be done, but here are some difficulties:
comparable
cannot be used in a type union- type constraints cannot be composed
- type constraints cannot be defined recursively
- type constraints cannot be negated
- there is no interface which matches structs in general
答案2
得分: 0
从Go 1.19开始,约束comparable
只能由严格可比较的类型实现,即保证==
和!=
在运行时不会引发恐慌的类型。
因此,这排除了接口。但它也排除了其他既不是严格可比较也不是接口的类型,例如[5]any
或struct{ Data any }
。
我不知道这是否对你来说足够好。否则,无法通过减法来定义约束类型集,即在集合表示法中,A \ B。根据规范:
> 接口类型由接口元素列表指定。接口元素可以是方法,也可以是类型元素,其中类型元素是一个或多个类型项的并集。类型项可以是单个类型或单个底层类型。
如果您需要从计算中排除某些类型的kinds,您可能需要回退到反射。还要考虑到,如果您的目标是解组JSON,使用any
约束和*T
可能是可接受的(==
检查两个指针是否指向同一个变量)。毕竟,您应该将一个可寻址的值传递给json.Unmarshal
:
// 为了说明目的而简化的代码
func (jit *JitJSON[T]) Unmarshal() (T, error) {
var val T
err := json.Unmarshal(jit.data, &val) // 无论如何都需要一个指针
if err != nil {
return val, err
}
jit.val = &val
return *jit.val, nil
}
英文:
As of Go 1.19 the constraint comparable
already can be implemented only by strictly comparable types, i.e. types for which ==
and !=
are guaranteed to not panic at run time.
So this does exclude interfaces. However it also excludes other types that aren't strictly comparable but also not interfaces either, e.g. [5]any
or struct{ Data any }
.
I don't know if this is good enough for you. Otherwise, there is no way to define a constraint type set in terms of subtractions, i.e. in set notation, A \ B. From the spec:
> An interface type is specified by a list of interface elements. An interface element is either a method or a type element, where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.
If you need to exclude some kinds of types from your computation you might have to fall back to reflection.
Consider also that if your goal is to unmarshal JSON, using any
constraint and *T
might be acceptable (==
checks if two pointers point to the same variable). After all, you are supposed to pass an addressable value to json.Unmarshal
:
// code simplified for illustrative purposes
func (jit *JitJSON[T]) Unmarshal() (T, error) {
var val T
err := json.Unmarshal(jit.data, &val) // needs a pointer anyway
if err != nil {
return val, err
}
jit.val = &val
return *jit.val, nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论