“在这个核心模型中,生命周期可形成子类型或区域包含关系。若 $\rho {long}\supseteq\rho {short}$,一个更长寿命的共享引用可安全缩短:”
形式陈述 ​
子类型关系
因此子类型通常首先是预序,而非语法类型上的偏序:互为子类型的两个类型未必字面相同;按
函数类型满足参数逆变、结果协变:
不可变积与记录通常按分量协变;记录还可有宽度子类型。含可写位置的引用类型一般不能简单协变,否则会破坏类型安全。
直觉
子类型不是“结构看起来像”或“类继承了谁”,而是上下文中的可替代性承诺。函数参数逆变最容易反直觉:一个能处理所有动物的函数当然能放到只会收到猫的位置,而只处理猫的函数不能冒充能处理所有动物的函数。结果则相反,返回更具体的猫可安全当作返回动物。定义中的方向由谁提供、谁消费数据决定,而不是由语法箭头机械决定。
例子与边界
结构记录中,若只读字段采用宽度子类型,则 {x:Int,y:Int}<:{x:Int}:需要 x 的代码可以忽略额外的 y。函数 Animal→Cat 可作为 Cat→Animal 使用,因为它接受范围更广、返回结果更具体。
边界是可变引用。若假设 Ref Cat <: Ref Animal,代码可通过 Ref Animal 写入一只狗,原持有者随后从 Ref Cat 读出狗,类型承诺被破坏;因此可读写引用通常不变。名义继承也不自动等于行为子类型,违反方法契约的子类不能仅凭语法继承获得可靠替代性。
推论与应用
子类型支撑面向对象接口、记录扩展、数值层次和 API 演化。所有权与借用还会把生命周期包含解释成引用可缩短使用区间的 region subtyping;独占 reborrow 与可变引用的 variance 另有别名约束,不能从普通协变机械推出。算法类型检查需要决定
它与参数多态是不同复用机制:前者允许较具体值向上转换,后者要求同一代码对所有类型参数统一工作。类型判断通过 subsumption 接纳子类型,而类型安全约束每条子类型规则必须保持运行时行为。
参考资料
- Benjamin C. Pierce, Types and Programming Languages, MIT Press, 2002,Chs. 15–18, subtyping, records, and variants。
- Robert Harper, Practical Foundations for Programming Languages, 2nd ed., Cambridge University Press, 2016,Chs. 21–24, subtyping and variance。