你正在使用的Go版本是什么( go version
)?
Go Playground, 1.21
这个问题在最新版本的发布中是否重现?
是的
你正在使用什么操作系统和处理器架构( go env
)?
N/A, Go Playground
你做了什么?
https://go.dev/play/p/Jhx6YGrz21s
你期望看到什么?
convert()
和 convert2()
都被编译器接受,并且在语义上是等价的。
你看到了什么?
convert2()
被编译器拒绝: cannot convert x (variable of type srcType constrained by string) to type dstType: cannot convert string (in srcType) to type []elType (in dstType)
细节
规范关于类型参数的类型转换如下所述:
此外,如果T或x的类型V是类型参数,那么如果以下任一条件适用,x也可以转换为类型T:
- V和T都是类型参数,并且V的类型集中的每个类型的值都可以转换为T的类型集中的每个类型。
考虑以下代码:
type charLike interface{ byte | rune }
func convert2[elType charLike, srcType string, dstType []elType](x srcType) dstType {
return dstType(x) // ERROR: cannot convert x (variable of type srcType constrained by string) to type dstType: cannot convert string (in srcType) to type []elType (in dstType)
}
在这里,srcType
的类型集应该是 string
,而 dstType
的类型集是 []rune|[]byte
。根据规范, string
可以转换为 []rune
和 []byte
:
非常量值x可以在以下任何情况下转换为类型T:
- ...
- x是一个字符串,而T是一个字节或rune的切片。
因此,假设编译器能够正确推断出 dstType
的类型集,转换应该是允许的。
我能够找到的最简单的问题示例是:
func convert2[elType byte, dstType []elType](x string) dstType {
return dstType(x) // ERROR: cannot convert x (variable of type string) to type dstType: cannot convert string to type []elType (in dstType)
}
消除elType的下一步使错误消失:
func convert2[dstType []byte](x string) dstType {
return dstType(x)}
所以看起来编译器无法将 dstType
的类型集与 elType
中的间接性展平。
此外,@Merovius 能够构造出两个语义上等价的例子,暗示这个问题在某种程度上是特定于类型转换操作的:
9条答案
按热度按时间vvppvyoh1#
dstType
的类型集合是[]elType
,而不是[]rune|[]byte
(规范):非接口类型项的类型集合是由该类型组成的集合。
给定
elType
是一个类型参数,我不确定这是否意味着转换操作应该被允许。直观上,很明显对于每个有效的convert2
示例化,elType
将与rune
或byte
相同,因此“直观”的类型集合是那个并集。但我不确定规范是否意味着实际的类型集合是那样的。我构造的原始示例(因此也是我试图用它们来说明的重点)与上面给出的有所不同:
被拒绝的: https://go.dev/play/p/DDn-SFzglUz
被接受的: https://go.dev/play/p/ling0sjUz8r
特别注意,函数示例化 也要求您明确指定
Dst
必须满足stringLike
(或在我的例子中的S
)。转换似乎确实相关,因为在转换不成立的情况下,示例化仍然成功。但我觉得这并不令人惊讶,考虑到它们使用了不同的条件。上面的转换条件已经给出,并且明确地用两个类型参数的类型集合来表述,而示例化条件则是关于类型参数满足约束的:替换后,每个类型参数必须满足相应类型的约束(如果需要的话进行示例化)。否则示例化失败。
直观上,它们应该意味着同样的事情,但当我涉及到类型参数、类型参数和类型集合时,我对规范的具体措辞感到困惑。特别是,我不太清楚对于一个类型参数类型的类型 参数(抱歉,这可能是令人兴奋的解析)满足约束是什么意思。
我构造这些示例的原因是因为对我来说,它们暗示了问题具有类似于在约束中显式提及方法的限制的结构。就像
是不允许的,但一旦我们将约束与
io.Reader
相交,就会变得允许:所以示例开始工作,一旦我们将约束与其他应该由并集隐含的接口相交。
db2dz4w82#
根据当前规范,这不是一个bug。一个更简单的例子:
同样适用于赋值:
(只是从 here 中举出的两个小例子。)
规则可能会在以后放宽,但我不知道这是否值得。
pkln4tw63#
dstType
的类型集是[]elType
,而不是[]rune|[]byte
(规范):非接口类型项的类型集是由该类型组成的集合。
好的观点。我认为如果复合类型上允许的操作集不依赖于它所组成的类型,那么这个声明就不会引起争议。而且这在很大程度上是正确的,我能想到的唯一例外是字符串切片转换。
然后我想问的是:规范是否打算保留类型参数和具体类型之间的差异?
fhity93d4#
是的,这很令人困惑——现在改变像[]elType这样的约束合法性已经太晚了,但也许我们可以为单例类型集添加一个验证检查(我还需要再多想一会儿)。
hwazgwia5#
问题可能是关于什么类型的组合满足类型参数?
[]eltype应该满足[]byte | []rune,如果eltype满足byte | rune。
可能类似于类型推断中使用的逻辑,切片类型构造函数可以看作是类型集合域上的双射。
这里的转换可能是特殊情况,并且在通用约束检查算法中没有简单地实现。
envsm3lx6#
@go101 仅供参考:您的示例作业与此问题无关:
类型参数是命名类型(规范)。命名类型始终不同于任何其他类型(规范)。永远不可能将一个命名类型的值(
A
)赋给另一个命名类型的变量(B
)。o75abkj47#
对于这个问题,查看一些简单的示例可能会有所帮助。在非泛型代码中,以下是有效的:
这个转换是有效的,因为“x 是一个整数、字节切片或 rune 的类型,T 是一个字符串类型”,并且因为
[]E
被认为是一个“字节切片”,实现(至关重要!)会查看E
的底层类型来做出这个判断。这种情况一直是这样,而且可以说没有很好地规定。泛型变体
也是被接受的,因为从
T
到字符串的转换如果对T
中的每个类型都是允许的,那么就只有一个类型:[]byte
。同样,也是可以的(将
E
全局定义为byte
),因为这只是f0
和f1
的组合。泛型变体
由于非显而易见的原因而失败——不过我认为这实际上只是一个错误:请注意
x
的类型是一个切片类型,它碰巧切片元素是一个类型参数。类型显然是一个切片,但它是字节切片吗?实现会查看E
的底层类型,而该类型恰好是一个接口,而不是byte
,因此失败了。如果我们转而查看核心类型,我们会发现它确实存在,且为byte
,然后这段代码就会被接受。(事实上,只需在实现中将正确的under
调用更改为coreType
就可以使此代码正常工作。)如上所述,我认为规范对于什么是slice of bytes
没有明确说明,我们应该澄清这一点并使其在整个规范中保持一致(这也影响到诸如append
、copy
等函数)。同样,变体
在使用实现中的相同更改下也可以正常工作。
所以我的倾向是将其视为一个错误:首先,规范没有足够精确地说明;其次,实现在泛型和非泛型类型之间并不完全一致。
我怀疑(但我还没有百分之百确定)我们可以通过这种更改来实现这一目标,因为这将使迄今为止被拒绝的代码起作用。
此外,我们可以认为这些代码(
f3
和f4
)应该可以工作,因为我们可以使用那些转换是有效的类型的示例化这些函数(也许在约束条件中适当地增强~
)。这可能是我们可以合理化这种更改的理由。这基本上遵循了 @Merovius 正在进行的关于类型集的论点;但有一个问题是如何准确地表述这一点。最后,所有这些都没有解决
E
受到byte | rune
约束的问题,就像原始问题中这个例子的情况一样,因为在那种情况下没有核心类型。我认为这是一个暂时的实现限制。回想一下最初的泛型提案,目标始终是允许对类型参数类型的值执行所有允许在各自类型集中的操作(假设我们真的意味着“示例化的”类型集,这是 Merovius 一直暗示的,我相信)。我们在很多情况下都在这样做,但肯定不是所有情况。在其他情况下逃脱的方法是求助于核心类型。这肯定是我们希望随着时间的推移澄清的事情之一,因为这可能有助于摆脱“核心类型”的概念,从而简化规范和语言(以更复杂的实现为代价)。ctrmrzij8#
后续:规范中没有明确定义"字节片段"(或"符文片段"),但过去的例子被添加以明确其含义:
因此,如果
E
的底层类型是byte
,则[]E
是 "字节片段"。ttcibm8c9#
这也与#50421有关。