Go中struct传值或传指针取决于结构体大小和是否需修改原值:小结构体(≤5字段、无大字段)且只读时传值;含大字段、需修改或带副作用时必须传指针,且须首行判空防panic。

传值还是传指针?先看结构体大小和是否需要修改
Go 所有参数都是值传递,struct 也不例外——传 Order 是拷贝整个结构体,传 *Order 是拷贝一个指针(通常 8 字节)。要不要加 *,核心就两条:结构体是否小(≤ 4–5 个字段,且不含大字段),以及函数是否要改原对象。
常见判断依据:
- 结构体含
sync.Mutex、map、[]byte(几 MB)、或字段超过 5 个基本类型 → 编译器可能直接报错或触发显著内存拷贝,必须用*T - 函数名含
Update、Set、Apply等动词 → 暗示副作用,该用*T - 结构体像
type Point struct{ X, Y int }(16 字节)→ 传值更安全、无 nil 风险、语义清晰 - 只读计算(如
userDisplayName(u User) string)→ 传值即可,避免解引用和空指针检查
传指针时必须检查 nil,否则 runtime panic
*T 参数不是“可选优化”,而是带责任的契约:你得自己确保它不为 nil,Go 不自动判空。一旦在函数里写 t.Field 或 *t,而 t == nil,立刻 panic: invalid memory address or nil pointer dereference。
正确做法是函数入口第一行就做防御:
func processOrder(o *Order) error {
if o == nil {
return errors.New("processOrder: o must not be nil")
}
// 后续逻辑
}
别依赖调用方“不会传 nil”——json.Unmarshal、HTTP handler 解析、DB scan 都可能产出 nil 指针。
嵌套结构体传参必须用具名类型,匿名 struct 不兼容
Go 的类型系统认“名”不只认“形”。即使两个匿名 struct 字段完全一样,只要声明位置不同,就是不同类型,不能互传:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type Config struct {
Mail struct { From, To string }
}
func StartMailer(m struct{ From, To string }) { ... }
StartMailer(config.Mail) // ❌ 编译错误:cannot use config.Mail as type struct{}
解决方法只有:提前定义导出的具名类型,并确保字段首字母大写(可被外部访问):
type Mail struct {
From string `json:"from"`
To string `json:"to"`
}
func StartMailer(m Mail) { ... } // ✅ 类型明确,可复用,支持 JSON 映射
再通过内嵌(embedding)接入主配置,既保持结构扁平,又满足类型一致性。
传值不等于数据隔离:slice 和 map 字段仍共享底层数组
这是最容易被忽略的坑。你以为传 User 是副本,很安全,结果 User.Data []int 的底层数组仍是同一块内存:
func modifyData(u User) {
u.Data[0] = 999 // ✅ 原对象 u.Data[0] 也被改了
u.Data = append(u.Data, 1) // ❌ 可能扩容,新 slice 头部不指向原数组,原对象不变
}
想真正隔离,得手动深拷贝:
newData := make([]int, len(u.Data)) copy(newData, u.Data) u.Data = newData
更可持续的做法是把可变引用字段设为私有,暴露 GetData() []int(返回副本)和 SetData()(带校验),而不是放任裸字段暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










