Go语言无内置深拷贝函数是设计选择,要求开发者明确数据复制需求;=赋值和copy()均为浅拷贝,仅复制引用;深拷贝需用ulule/deepcopier(推荐)、gob/json序列化或手动反射实现,但各有局限。

Go 语言没有内置的深拷贝函数,这不是遗漏,而是设计选择:它要求你明确知道什么时候需要复制底层数据,而不是隐式地、昂贵地、不可控地做一次全量递归复制。
直接赋值 = 和 copy() 都是浅拷贝
只要结构体里包含 []T、map[K]V、*T、chan T 或 interface{},用 = 赋值就只是复制了引用。修改副本里的切片元素或 map 值,原始对象会同步变化。
-
copy(dst, src)只对切片有效,且只复制元素值——如果元素是指针或 map,照样共享底层 - 对结构体指针解引用后赋值(如
*dst = *src)仍是浅拷贝,嵌套引用类型不会被展开 - 常见错误现象:
json.Unmarshal后修改返回值,意外改到了缓存里的原始数据
用 ulule/deepcopier 做类型安全的编译期深拷贝
这是目前生产环境最推荐的方式:不依赖运行时反射,零性能开销,字段映射、忽略、重命名都支持,还能处理嵌套结构体和指针。
- 必须确保源结构体字段导出(首字母大写),目标结构体字段可写
- 安装:
go get github.com/ulule/deepcopier - 基础用法:
deecopier.Copy(&src).To(&dst),注意两个参数都必须传指针 - 字段重命名示例:
type DTO struct { Name string `deepcopier:"field:FullName"` } - 不支持未导出字段、func、channel、unsafe.Pointer —— 这是 Go 的限制,不是库的问题
用 encoding/gob 或 json 做“伪深拷贝”要当心边界
它们本质是序列化 + 反序列化,能自动穿透嵌套,但不是真正的内存级深拷贝,且有明显副作用。
立即学习“go语言免费学习笔记(深入)”;
-
gob支持未导出字段,但无法处理channel、func、unsafe.Pointer;需提前注册自定义类型 -
json更常用,但会丢掉未导出字段、把time.Time变成字符串、把 nil slice/map 反序列化为空实例(掩盖逻辑 bug) - 性能差:比手动复制慢一个数量级以上,频繁调用会显著拖慢接口
- 仅适合低频、非关键路径、或作为临时兜底方案(比如测试中快速克隆配置)
自己写反射深拷贝?除非你真清楚代价
通用反射深拷贝代码不难写,但实际落地时坑远多于便利。
- 循环引用会导致无限递归 panic(比如 struct A 包含 *B,B 又包含 *A)
- 无法访问未导出字段,所有私有状态都会丢失或被跳过
- 对
func、channel、unsafe.Pointer等类型只能 panic 或静默跳过 - 反射本身有 5–10 倍性能损耗,且编译器几乎无法优化
- 真正需要它的场景极少——绝大多数时候,你应该重构结构体,让它更扁平、更少嵌套、更少指针
深拷贝不是“有没有”的问题,而是“该不该做”和“怎么做才可控”的问题。Go 把决定权留给你,意味着你得看懂自己的数据流向,而不是依赖一个黑盒函数帮你兜底。


















