Go 1.22 的新增功能系列之二:reflect.TypeFor
共 2494字,需浏览 5分钟
·
2024-04-28 09:48
Go 1.22 的第一个候选版本已经发布,这意味着最终版本即将发布,现在是我在博客中介绍我在这个周期中所做工作的时候了。像往常一样,我的贡献很小,但它们是我的,所以我将从幕后的角度来谈谈它们。首先是reflect.TypeFor。
这是整个函数:
// TypeFor returns the [Type] that represents the type argument T.
func TypeFor[T any]() Type {
return TypeOf((*T)(nil)).Elem()
}
很简单!测试代码比实际函数本身长大约十倍。
当您想要为某种类型创建 reflect.Type
对象时,例如 var intType = reflect.TypeFor[int]()
或 var errorType = reflect.TypeFor[error]()
,请使用此函数。
将其添加到反射包中的提案由 Josh Bleecher Snyder 提出。它标志着泛型通过标准库的不断进步又迈出了一步。
该提案最困难的部分可能是为该函数找到正确的名称。 reflect.TypeOf
将是该函数最自然的名称,但它已被现有函数用于创建 reflect.Type
对象。考虑的替代方案包括:
StaticTypeOf
MakeType
TypeOfT
TypeFrom
GetType
AsType
ToType
NewType
TheType
Types
ConstantType
T
命名事物仍然是计算机科学中的两个难题之一。
我扔掉了获胜的名字,但那时我们只是浏览所有可能的形容词和介词的列表,所以最终有人会选择 TypeFor
。
总的来说,我反对“无用地使用泛型”,即使用泛型函数,即使它没有添加任何类型安全性并且没有或只有最小的长度节省。例如,制作 json.Marshal
的泛型版本对泛型来说是无用的,因为它实际上并没有添加任何类型安全性。然而,在这种情况下,为接口构建 reflect.Type
的正确方法相当模糊,所以我认为值得添加。
这个问题可以追溯到 Go 中的反射第一定律:“反射从接口值到反射对象。”当您调用 reflect.TypeOf(0)
时,您会得到您所期望的:类型 int
的 reflect.Type
对象。但如果您致电 reflect.TypeOf(err)
,您可能会对所得到的结果感到惊讶。您不会获取包含类型 error
的对象,而是获取具有底层具体类型 err
的对象。这是因为 error
是一个接口值,而 Go 中调用函数时,函数的接口参数会根据需要进行转换,所以错误接口丢失,只有具体值对 reflect.TypeOf
。更糟糕的是,如果 err
为零,您会从 reflect.TypeOf
返回零。
Chris Siebenmann 对正在发生的事情以及如何解决它做了很好的解释:
因为 reflect.TypeOf() 传递了一个“ interface{} ”(现在也称为“ any ”),所以它不能直接给你一个reflect.Type接口,因为该接口类型在转换为“ interface{} ”时将会丢失。相反, reflect.TypeOf() 返回底层非接口类型(或 nil)。正如我们在“ nil ”只是某种类型的上下文中所看到的,要解决这个问题,您必须传递一个指向接口的指针(好吧,接口的值),返回类型“指向”的指针,然后再次通过反射取消引用原始类型......
在 Go 1.22 之前,如果要为接口创建 reflect.Type
,解决方案是调用 reflect.TypeOf((*TheInterface)(nil)).Elem()
。这可行,但不是很直观。我知道我很难在我自己的使用反射包的代码中弄清楚它。
当 reflect.TypeFor
被提出时,还有一个替代提案,它也添加了reflect.ValueOf函数的通用版本。我在问题评论中认为这是一个错误:
粗略地看一下标准库,就会发现有许多地方可以用 reflect.T[whatever]() 来替换,以便更加清晰。reflect.GenericValueOf 更难限定,因为在某些地方接口类型显然是我们想要的,但在很多情况下,底层的具体类型才是目标。添加 reflect.GenericValueOf 会在阅读代码时增加一定程度的歧义:作者是否真的想要这里的接口类型,还是他们只是看到泛型并假设该函数更新,因此更好?它添加了第二种做事方式,但并没有 100% 清楚其意图。所以我倾向于在 reflect.T 上+1,但在 reflect.GenericValueOf 上+0。
有时,Go 标准库最好的东西就是所有被遗漏的东西。在我看来, reflect.GenericValueOf
会越过模糊线,变成“无用的泛型使用”。
这就是 reflect.TypeFor
的故事。标准库中的每个函数背后都有一个故事。