Assigning a value literal to a struct field of a generic type without running into an IncompatibleAssign error

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

Assigning a value literal to a struct field of a generic type without running into an IncompatibleAssign error

问题

这是可以的:

type constraint interface {
	~float32 | ~float64
}

type foo[T constraint] struct {
	val T
}

func (f *foo[float64]) setValToPi() {
	f.val = 3.14
}

然而,如果我将constraint更改为包括int类型,就会遇到错误:

type constraint interface {
	~float32 | ~float64 | ~int
}

type foo[T constraint] struct {
	val T
}

func (f *foo[float64]) setValToPi() {
	f.val = 3.14 // IncompatibleAssign: cannot use 3.14 (untyped float constant) as float64 value in assignment
}

为什么包含不属于同一“类型组”的类型的约束会导致此错误,我应该如何处理它?

英文:

This is fine:

type constraint interface {
	~float32 | ~float64
}

type foo[T constraint] struct {
	val T
}

func (f *foo[float64]) setValToPi() {
	f.val = 3.14
}

However, if I change constraint to also include int types, I run into an error:

type constraint interface {
	~float32 | ~float64 | ~int
}

type foo[T constraint] struct {
	val T
}

func (f *foo[float64]) setValToPi() {
	f.val = 3.14 // IncompatibleAssign: cannot use 3.14 (untyped float constant) as float64 value in assignment
}

Why do constraints that include types that don't belong to the same "type group" cause this error, and how should I deal with it?

答案1

得分: 4

这个语法:

func (f *foo[float64]) setValToPi() {
    // ...
}

只是一个方法声明。它不会实例化泛型类型foo。方括号中的标识符float64是类型参数的名称。它可以像类型定义中一样是T

就好像你写了:

type foo[float64 constraint] struct {
    val float64
}

由于float64是一个预声明的标识符,你可以用类型参数名称来隐藏它。

因此,在方法setValToPi中,关于val的类型的唯一已知信息是它被约束为constraint,例如~float32 | ~float64 | ~int的并集。

如果你将方法声明更改为:

func (f *foo[T]) setValToPi() {
    // ...
}

你将得到相同的错误,只是用T代替:

cannot use 3.14 (untyped float constant) as T value in assignment

错误是由于3.14(一个无类型的浮点常量)不能总是分配给所有可能的foo[T]实例,特别是当T确实是~int时。

带有float64标识符作为类型参数的 Playground:https://gotipplay.golang.org/p/1EuAsSKdihK

一个解决方法是使该方法接受类型参数类型的值(除了使用不那么令人困惑的标识符):

func (f *foo[T]) SetValue(val T) {
    f.val = val
}

当然,这意味着你不能设置固定值,比如3.14,但正如解释的那样,这本身就是一个错误。其他可能的解决方案是使用any/interface{}作为字段类型。可以参考这里的一些灵感:这里这里

英文:

This syntax:

func (f *foo[float64]) setValToPi() {
    // ...
}

Is simply a method declaration. It does not instantiate the generic type foo. The identifier float64 you have within square brackets is the name of the type parameter. It could be T, as in the type definition, just as well.

It's like you wrote:

type foo[float64 constraint] struct {
    val float64
}

Since float64 is a predeclared identifier, you can shadow it with a type parameter name.

So within the method setValToPi, the only known information about the type of val is that it is constrained to constraint, e.g. the union of ~float32 | ~float64 | ~int.

If you change the method declaration to:

func (f *foo[T]) setValToPi() {
    // ...
}

You will get the same error with T instead:

> cannot use 3.14 (untyped float constant) as T value in assignment

And the error is given by the fact that 3.14 (an untyped floating-point constant) can't be always assigned to all possible instances of foo[T], specifically the one where T is indeed ~int.

Playground with the float64 identifier as type param: https://gotipplay.golang.org/p/1EuAsSKdihK

A solution would be to make the method accept a value of type parameter type (beside using less confusing identifiers):

func (f *foo[T]) SetValue(val T) {
    f.val = val
}

This of course means you can't set fixed values like 3.14, but as explained, this is a mistake in the first place. Other possible solutions are using any/interface{} as the field type. For some inspiration, see here or here.

huangapple
  • 本文由 发表于 2021年12月16日 22:19:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/70380575.html
匿名

发表评论

匿名网友

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

确定