值接收者无法修改原结构体,指针接收者才能真正修改;小结构体(≤24字节)优先值接收者以提升性能与安全,大结构体或需修改状态时必须用指针接收者。
这是最直观、最常踩坑的区别:用 func (u User) SetName(name string) 写的方法,无论你怎么赋值 u.Name = name,调用后原始 User 实例的 Name 一点没变;而换成 func (u *User) SetName(name string),就能直接生效。
u.Name = ... 等价于 (*u).Name = ...
当你写好一个接口 type Namer interface { GetName() string },却在赋值时遇到 cannot use u (type User) as type Namer in assignment: User does not implement Namer,八成是因为 GetName 是用指针接收者定义的:func (u *User) GetName() string。
User 的方法集只含值接收者方法*User 的方法集包含值 + 指针接收者方法*User 能实现该接口,User{} 不行,&User{} 才行User{Name:"A"} 是不可寻址的,连 & 都取不了,根本没法调指针接收者方法别一看到“指针=高效”就全上指针。像 type Point struct { X, Y int }、type RGB [3]uint8、type UserID string 这类 ≤ 24 字节且无指针字段的类型,值接收者是首选。
(p Point
) Distance() 不会竞争func (c Config) WithTimeout(d time.Duration) Config
extraHeader.Write() 就靠值接收者把 map header 留在栈上结构体超过 64 字节(比如含 slice、map、大数组或嵌套结构体),或者方法逻辑本身就要变更字段、追加数据、重分配底层数组,那就别犹豫了,上指针。
func (s *IntSlice) Append(v int) 必须用指针,因为 append 可能扩容,会改 s 的 header(len/cap/ptr)真正难的不是记住规则,而是判断一个类型“本质是不是可变的”。比如 type Cache map[string]int,它看起来像值,但底层是引用类型;func (c Cache) Set(k string, v int) 看似只读,其实已经改了 map 的内容——这种隐式可变性,比显式字段赋值更易被忽略。