4.4.4-12 类型的等价规则

Go 语言采用的是结构化类型系统, 也就是说判断两个类型是否相等考虑的是类型的内在结构,而与类型的名字或具体的声明位置无关。判断两个类型是否等价的入口函数定义在$GCROOT/compile/internal/type2/api.go中:

func Identical(x, y Type) bool { /* ... */ }

详细逻辑在$GCROOT/compile/internal/types2/predicates.go中:

func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
	// types must be expanded for comparison
	x = expandf(x)
	y = expandf(y)

	if x == y {
		return true
	}

	switch x := x.(type) {
	case *Basic:
		// Basic types are singletons except for the rune and byte
		// aliases, thus we cannot solely rely on the x == y check
		// above. See also comment in TypeName.IsAlias.
		if y, ok := y.(*Basic); ok {
			return x.kind == y.kind
		}

	case *Struct:
		// Two struct types are identical if they have the same sequence of fields,
		// and if corresponding fields have the same names, and identical types,
		// and identical tags. Two embedded fields are considered to have the same
		// name. Lower-case field names from different packages are always different.
		if y, ok := y.(*Struct); ok {
			if x.NumFields() == y.NumFields() {
				for i, f := range x.fields {
					g := y.fields[i] // 对应位置的属性
					if f.embedded != g.embedded ||
						cmpTags && x.Tag(i) != y.Tag(i) ||
						!f.sameId(g.pkg, g.name) ||
						!check.identical0(f.typ, g.typ, cmpTags, p) {
						return false
					}
				}
				return true
			}
		}

	// 以下每个 case 内部的代码都省略了,详细实现参考源文件
	case *Array:
	case *Slice:
	case *Pointer:
	case *Tuple:
	case *Signature:
	case *Sum:
	case *Interface:
	case *Map:
	case *Chan:
	case *Named:
	case *TypeParam:
	case *bottom, *top:
	case nil:
	default:
		unreachable()
	}

	return false
}

该函数内部的每个 case 对应一种类型,然后使用递归的方式检查类型的内部结构是否一致,我们留下Basic及Struct类型来做参考。两个Basic如果是同一种类型,则彼此等价;而两个Struct类型等价需要符合如下条件:

  1. 属性数目相同,并且拥有相同的序列

  2. 对于同一个位置的两个属性,其名称相同,类型相同,并且 tag 相同

  3. 如果两个 struct 在不同的包中定义,则不能包含私有属性

我们看如下代码:

// package user
type User struct { // User 定义在 package user 中
	Name string
}

// package main
type People struct { // People 定义在 package main 中,其内部结构与 user.User 一致
	Name string
}

func buildProfile(u struct { // 函数的参数类型是一个无名类型,其结构与 user.User、People 均相同
	Name string
}) {
	fmt.Printf("%v\n", u)
}

func main() {
	// OK, 将 user.User 作为函数参数是合法的
	buildProfile(user.User{})

	// OK, 将 People 作为函数参数是合法的
	buildProfile(People{})

	// OK, 将无名类型字面量作为函数参数是合法的
	buildProfile(struct {
		Name string
	}{
		Name: "Jerry",
	})

	// OK, 可以直接将 user.User 类型转换为 People 类型
	var _ user.User = user.User(People{})
}

通过上述代码可以发现,只要类型的结构是一致的,那么就是等价的。

除了结构化类型系统之外,在编程语言中常用的类型系统还包括Nominal Type System以及Duck Typing, 前者根据类型声明的位置以及名字来区分(Java),而后者根据类型的行为来区分(Python)。

随着对泛型支持,类型等价性比较会变得更加复杂,这在后面方法的等价性校验有更详细的介绍,现在该功能还在开发之中,可能后期完成之后会合并到一起。

最后更新于