Go中结构体传值或传指针需依场景而定:小结构体(如Point)可值传;含切片、map、channel或超16字节时应传*Struct,避免拷贝开销与副本误改;json.Unmarshal等必须传指针,且须判空防panic。
go 里传数据不是单一答案,得看场景:是函数内部改变量、http 请求发 json、tcp 传文件,还是跨中间件带个 trace id?选错方式轻则 panic,重则内存泄漏或 data race。
函数参数传结构体要不要加 *?
结构体字段少、体积小(比如 type Point { X, Y int }),值传没问题;但只要含切片、map、channel、指针字段,或本身超过 16 字节,就该传 *T——否则每次调用都拷贝底层数据,性能掉得明显,且你改的只是副本。
- 必须传指针的典型场景:
json.Unmarshal要求*T,否则解析失败不报错但字段为空 - 修改原值前务必判空:
if u == nil { return },不然u.Name = "x"直接 panic - 别为了“统一风格”全传指针:
func calc(x, y int) int没必要改成func calc(x, y *int)
context.WithValue 能不能塞用户 ID?
能,但只限于请求生命周期内的只读元数据,比如 traceID、userID、tenantID。它不是函数参数的替代品,更不能塞 *sql.DB 或配置 struct。
- key 必须是未导出类型:
type userIDKey struct{},禁用"user_id"字符串——否则包间 key 冲突无声覆盖 - 取值必须带类型断言和非空判断:
if id, ok := ctx.Value(userIDKey{}).(string); ok && id != "" - 多个字段打包成不可变 struct 一次注入:
type reqMeta { ID, Lang string },别链式调用三次WithValue
HTTP 传 JSON 数据,http.NewRequest 容易漏哪?
最常漏的是 Content-Type: application/json 头,或者忘了用 bytes.NewBuffer 包装 json.Marshal 结果,导致服务端收不到 body。
-
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))——nilbody 会静默变成 GET 请求 - 服务端用
json.NewDecoder(r.Body).Decode(&v),别用ioutil.ReadAll全读再解析,大文件会爆内存 - 客户端记得设超时:
http.Client{Timeout: 10 * time.Second},否则卡死连接池
TCP 传大文件,为什么不能先 os.ReadFile 再发?
因为会把整个文件加载进内存,2GB 视频直接 OOM。必须流式读写,边读边发,用固定 buffer 复用。
立即学习“go语言免费学习笔记(深入)”;
- 发送端用
io.Copy(conn, file),接收端用io.Copy(file, conn),零中间分配 - 文件名要单独先传(比如固定 256 字节头),别混在数据流里靠分隔符识别——网络丢包后无法同步
- 别自己写
for { n, _ := f.Read(buf); conn.Write(buf[:n]) },io.Copy内部已优化 buffer 复用逻辑
真正难的不是“怎么传”,而是判断该用哪条路:函数内改状态就传指针,跨服务传业务数据走 HTTP/JSON,长连接双向通信选 WebSocket,只带几个字符串元数据才轮到 context.WithValue。混用或误用,后面 debug 花的时间远超写代码本身。


















