为什么 []MyType 不是 []interface{} 的子类型?

在一个平凡的工作日,我尝试将一个自定义类型的 slice([]MyType)赋值给一个接收 []interface{} 类型参数的函数。然而 IDE 却告诉我不能这样使用。乍一看,interface{} 是 MyType 的父类型,那么 []interface{} 自然也应该是 []MyType 的父类型,合情合理。那么问题在哪呢?

为什么 []MyType 不是 []interface{} 的子类型?
Photo by Ryan Quintal / Unsplash

在一个平凡的工作日,我尝试将一个自定义类型的 slice([]MyType)赋值给一个接收 []interface{} 类型参数的函数。然而 IDE 却告诉我不能这样使用。

乍一看,interface{}(Go 语言中的空接口类型)是 MyType 的父类型,那么 []interface{} 自然也应该是 []MyType 的父类型,合情合理。于是我将这个报错发到了公司的“迷惑代码大赏”群。在回帖中,我发现事情比我想象的更加复杂:如果 []interface{} 是 []MyType 的父类型,那么以下代码应当成立:

func G() {
    v := []int{1,2,3}
    F(v)
    fmt.Println(v)
}

func F(v []interface{}) {
    // string is a subtype of interface{}, so this should be valid
    v[0] = "Oops"
}

然而我们显然可以看出这段代码是不成立的,那么一定是哪里出了问题。

我们知道,根据里氏代换原则,子类可以当成父类使用。假如 B 是 A 的子类,那么用 A 的地方一定可以用 B 来替换。这里的替换,包含了 A 所具有的一切方法(method)。当我们谈论类与对象时,这非常好理解,大家也都对此非常熟悉。不过,函数也是一种类型。当 A 和 B 用作函数的参数和返回值时,函数的继承关系如何呢?也就是说,func() A、func() B 及 func (A)、func(B) 具有怎样的继承关系呢?


让我们先来讨论前者,即 func() A、func() B 具有怎样的继承关系。如果使用 func() A 的地方一定可以用 func() B 替换,而使用 func() B 的地方不能用 func() A 替换,那么 func() A 就是 func() B 的父类了。看看事实是否如此吧!

使用”,意味着将其作为参数参与运算,不论其在语法上的表象是作为函数的参数还是表达式中的操作数(operand)。因为一个 a+b 的表达式,也可以表示为 Add(a, b) 。因此我们就将其作为函数的参数,这样比较直观。

另外,由于 A、B 过于抽象,我们使用一个现实中的例子来继续后续的讨论: io.Reader 是 Go 语言中人尽皆知的一个接口,它具有一个 Read() 方法。 *bytes.Bufferio.Reader 的子类型。

以下显然是成立的:

func F() *bytes.Buffer {
}

func Use(f func() io.Reader) {
    reader := f()
    reader.Read()
}

func main() {
    Use(F)
}

以下是不成立的:

func F() io.Reader {
}

func Use(f func() *bytes.Buffer) {
    buffer := f()
    _ = buffer.String()
}

func main() {
    Use(F)
    // Cannot use 'F' (type func() io.Reader) as the type func() *bytes.Buffer
}

因此,我们发现 func() *bytes.Buffer 是 func() io.Reader 的子类型。即父子类型的关系,在类型作为函数返回值时依然保持。如果 A 是 B 的父类型,则函数 func() A是函数 func() B 的父类型。


然后我们来看看当类型作为函数的参数时,父子类型的关系是否依然保持。以下是成立的:

func F(r io.Reader) {
	r.Read([]byte{})
}

func Use(f func(*bytes.Buffer)) {
	b := new(bytes.Buffer)
	f(b)
}

func main() {
	Use(F) // 会报错,因为 Go 的类型是“不变”(invariant)的。但是原理上是没问题的
}

以下是不成立的:

func F(r *bytes.Buffer) {
	_ = r.String()
}

func Use(f func(io.Reader)) {
	r := csv.NewReader()
	f(r)
}

func main() {
	Use(F)
}

因此,我们发现 func(io.Reader) 是 func(*bytes.Buffer) 的子类型。即父子类型的关系,在类型作为函数参数时发生逆转。如果 A 是 B 的父类型,则函数 func(A)是函数 func(B) 的子类型。


下面我们开始证明为什么 []MyType 不能当 []interface{} 用,即为什么 []MyType 不是 []interface{} 的子类型了:

众所周知,slice 具有如下两个基本操作:

as := make([]A, 10)
a := as[0] // func Get(as []A, i int) A
as[1] = a // func Set(as []A, i int, a A)

抛去语法的外衣,可以看为一个 Get 函数和一个 Set 函数。

若要让 []MyType 是 []interface{} 的子类型,则 []MyType 必须实现 []interface{} 的所有方法。即:

  • func Get(as []MyType, i int) MyType 必须是 func Get(as []interface{}, i int) interface{} 的子类 1️⃣
  • func Set(as []MyType, i int, a MyType) 必须是 func Set(as []interface{}, i int, a interface{}) 的子类 2️⃣

又已知 MyType 是 interface{} 的子类,那么:

  • f(interface{}) 必定是 f(MyType) 的子类 3️⃣
  • f()MyType 必定是 f() interface{} 的子类 4️⃣

即:

  • Get() MyType 必定是 Get() interface{} 的子类 5️⃣
  • Set(interface{}) 必定是 Set(MyType) 的子类(而这与上面的1️⃣矛盾

通过以上形式化证明我们发现:[]MyType 不是 []interface{} 的子类。

但是,这仅仅是因为 MyType 出现在了 Set 的参数中而引起的。也就是说,如果有个只读的切片类型,表示为 ro []MyType,那么 ro []MyType 会是 ro []interface{} 的子类型

其他语言是如何解决这个问题的呢?欢迎评论区补充。