结论:方法集是类型可调用方法的集合:类型 T 的方法集只含值接收者方法,*T 的方法集含值接收者和指针接收者方法。因此把值赋给接口时只有值接收者方法参与匹配——若方法用指针接收者声明,T{} 不实现该接口,&T{} 才实现。
展开:规则背后的原因:接口变量内部是(类型, 值副本)对,存进接口的值与原变量脱钩,若允许通过接口调用指针接收者方法,修改的是副本,调用者看不到效果——Go 索性在类型系统禁止,避免静默错误。注意对称的日常现象:直接对可取地址的变量 v.PtrMethod() 编译器自动插入 &v(语法糖,与方法集无关);但 map 里的值、range 的循环变量不可寻址,同样写法编译失败。工程建议:1)需要修改接收者状态、或结构体较大时统一用指针接收者(混用值/指针接收者是常见坏味道);2)纯函数式小类型(如 time.Duration 的运算)用值接收者;3)定义接口的实现方时先想清楚"值语义还是实体语义"——值语义(不可变、可比较)用值接收者,实体语义用指针。易错点:把 []T 直接赋给 []I(接口切片)不成立,与方法集无关,是数组协变问题,需逐个转换。
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
var _ interface{ Inc() } = &Counter{} // OK
// var _ interface{ Inc() } = Counter{} // 编译错误:Inc 是指针接收者
追问方向:接口值的内部表示(eface/iface 与 itab)、非空接口断言的代价。