通用类型不属于接口类型

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

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.Kindreflect.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 = &amp;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 &lt;nil&gt;. 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(&amp;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]anystruct{ 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, &amp;val) // needs a pointer anyway
	if err != nil {
		return val, err
	}
    jit.val = &amp;val
	return *jit.val, nil
}

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

发表评论

匿名网友

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

确定