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 {returntrue }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) {returnfalse } }returntrue } }// 以下每个 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:casenil:default:unreachable() }returnfalse}
该函数内部的每个 case 对应一种类型,然后使用递归的方式检查类型的内部结构是否一致,我们留下Basic及Struct类型来做参考。两个Basic如果是同一种类型,则彼此等价;而两个Struct类型等价需要符合如下条件:
属性数目相同,并且拥有相同的序列
对于同一个位置的两个属性,其名称相同,类型相同,并且 tag 相同
如果两个 struct 在不同的包中定义,则不能包含私有属性
我们看如下代码:
// package usertypeUserstruct { // User 定义在 package user 中 Name string}// package maintypePeoplestruct { // People 定义在 package main 中,其内部结构与 user.User 一致 Name string}funcbuildProfile(u struct { // 函数的参数类型是一个无名类型,其结构与 user.User、People 均相同 Name string}) { fmt.Printf("%v\n", u)}funcmain() {// 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{})}