min 的两个参数和返回值泛型的唯一约束是它们三者都必须是同样的类型 T,而这个 T 需要满足 Comparable。只要满足这个要求,T 可以是任意类型,它可以是 Int,Float,String 或者甚至是在编译时未知的定义在其他模块的某个类型。也就是说,编译器缺乏两个关键的信息,这导致它不能直接为这个函数生成代码:
编译器不知道 (包括参数和返回值在内的) 类型为 T 的变量的大小
编译器不知道需要调用的 < 函数是否有重载,因此也不知道需要调用的函数的地址。
Swift 通过为泛型代码引入一层间接的中间层来解决这些问题。当编译器遇到一个泛型类型的值时,它会将其包装到一个容器中。这个容器有固定的大小,并存储这个泛型值。如果这个值超过容器的尺寸[…]
“协议目击表提供了一组映射关系,通过这组映射,我们可以知道泛型类型满足的协议 (编译器通过泛型约束可以静态地知道这个信息) 和某个具体类型对于协议功能的具体实现 (这只在运行时才能知道) 的对应关系。实际上,只有通过目击表我们才能查询或者操作某个值。我们无法在不加约束地定义一个 <T> 参数的同时,还期望它能对任意实现了 < 的类型工作。如果没有满足 Comparable 的保证,编译器就不会让我们使用 < 操作,这是因为没有目击表可以让我们找到正确的 < 的实现。这就是我们说泛型和协议是紧密着联系的原因,除了像是 Array<Element> 或者 Optional<Wrapped> 这样的容器类型,脱离了使用协议来约束泛型,泛型所能做的事情也就非常有限了。
func min<T: Comparable>(_ x: Box_T, _ y: Box_T,
valueWTable_T: VTable, comparableWTable_T: VTable)
-> Box_T
{
let xCopy = valueWTable_T.copy(x)
let yCopy = valueWTable_T.copy(y)
let result = comparableWTable_T.lessThan(yCopy, xCopy) ? y : x
valueWTable_T.release(xCopy)
valueWTable_T.release(yCopy)
}
泛型参数的容器结构和我们在下一章将要提到的协议类型中使用的“存在容器” (existential containers) 有些相似,但是并不完全一样。一个存在容器中不仅会有值的存储,还可能在一个结构体中存在指向目击表的指针。而泛型参数的容器只会包含值存储,目击表是被单独存储的,这样泛型函数中同样类型的其他变量就可以共享这个目击表了
如果你想进一步了解泛型系统的工作方式的话,Swift 编译器的开发者 Slava Pestov 和 John McCall 在 2017 年的 LLVM 开发者会议上有一个关于这个专题的演讲。我们十分推荐你进行观看。
摘录来自: Chris Eidhof. “Swift 进阶。” Apple Books.