传结构体指针更省内存,因值传递会完整复制整个结构体,而指针仅传8字节;含sync.Mutex、需修改原字段、超128字节或接口实现要求时必须用指针。
Go 中结构体是值类型,func f(s MyStruct) 会完整复制整个结构体到栈上。如果结构体含大量字段、切片或嵌套大对象(比如含 []byte 或 map[string]interface{}),一次调用就可能拷贝几百字节甚至几KB——不仅浪费内存,还拖慢函数调用速度。而传指针 *MyStruct 始终只传 8 字节(64 位系统),复制开销恒定。
以下情况不传指针会出错或低效:
sync.Mutex 或其他不可复制类型(编译报错:cannot assign to struct containing sync.Mutex)*T 实现了某接口,你却传 T 值,会导致 cannot use t (type T) as type Interface
传指针本身不危险,但解引用前未判空会 panic。常见错误是忽略零值指针:
func processUser(u *User) {
fmt.Println(u.Name) // 如果 u == nil,这里 panic: invalid memory address
}
正确做法:
if u == nil 检查,并明确返回错误或 panic 带提示NewUser())统一返回 *User,避免裸写 &User{} 后忘记检查Profile *Profile),注意深层字段是否为 nil,不要无脑链式访问 u.Profile.AvatarURL
用 benchstat 对比两种方式(结构体含 1000 个 int64 字段,约 8KB):
func BenchmarkStructByValue(b *testing.B) {
s := makeLargeStruct()
for i := 0; i < b.N; i++ {
consumeByValue(s)
}
}
func BenchmarkStructByPtr(b *testing.B) {
s := makeLargeStruct()
for i := 0; i
< b.N; i++ {
consumeByPtr(&s)
}
}
结果典型差异:
BenchmarkStructByValue-8:~350 ns/op,分配 ~8KB 内存/次BenchmarkStructByPtr-8:~2 ns/op,分配 0 B真正容易被忽略的是:哪怕结构体只有几个字段,只要它被高频调用(如 HTTP handler 中每请求一次),累积的栈复制和 GC 压力也会明显上升。别只盯着单次“看起来不大”。