值接收者无法修改原结构体,指针接收者才能真正修改;小结构体(≤24字节)优先值接收者以提升性能与安全,大结构体或需修改状态时必须用指针接收者。

值接收者改不了原结构体,指针接收者才能真修改
这是最直观、最常踩坑的区别:用 func (u User) SetName(name string) 写的方法,无论你怎么赋值 u.Name = name,调用后原始 User 实例的 Name 一点没变;而换成 func (u *User) SetName(name string),就能直接生效。
- 值接收者操作的是栈上副本,方法退出即销毁
- 指针接收者拿到的是内存地址,
u.Name = ...等价于(*u).Name = ... - 别指望“自动返回副本”——Go 不像 Rust 那样强制 move 语义,不返回就真的丢了
接口实现失败?大概率是接收者类型不匹配
当你写好一个接口 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 字节且无指针字段的类型,值接收者是首选。
- 拷贝开销极低(通常 1–3 个机器字),比解引用还省事
- 天然并发安全:多个 goroutine 同时调用
(p Point) Distance()不会竞争 - 避免意外共享:明确表达“我只读不改”,比如
func (c Config) WithTimeout(d time.Duration) Config - 编译器更容易做逃逸分析优化——标准库
extraHeader.Write()就靠值接收者把 map header 留在栈上
大型结构体或需修改状态?必须用指针接收者
结构体超过 64 字节(比如含 slice、map、大数组或嵌套结构体),或者方法逻辑本身就要变更字段、追加数据、重分配底层数组,那就别犹豫了,上指针。
立即学习“go语言免费学习笔记(深入)”;
- 避免不必要的内存拷贝:复制一个 2KB 的结构体远比传一个 8 字节指针贵
- 切片类方法要小心:
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 的内容——这种隐式可变性,比显式字段赋值更易被忽略。









