go语言无内置深拷贝,=赋值等均为浅拷贝;reflect.copy不支持结构体,手动reflect.set仍是浅层;ulule/deepcopier通过编译期生成实现安全高效深拷贝。

Go 语言没有内置深拷贝机制,所有 = 赋值、copy()、甚至 *dst = *src 都只是浅拷贝——只要结构体里含 []T、map[K]V、*T、chan T 或 interface{},改副本就等于改原数据。
为什么不能直接用 reflect.Copy 处理结构体深拷贝
reflect.Copy 只支持切片之间复制,对结构体完全无效;而手动用 reflect.Value.Set() 直接赋值仍是浅层字段拷贝,嵌套的指针、map、切片依然共享底层数据。常见错误现象是:DTO 层修改了 Tags []string,结果缓存里的原始 User 对象的 Tags 也变了。
-
reflect.ValueOf(src).Set(reflect.ValueOf(dst))编译不通过(类型不匹配) - 对结构体字段循环调用
dstField.Set(srcField)仅复制字段值,不递归处理srcField内部的引用类型 - 遇到
nil指针或未初始化 map 时 panic,比如src.Addr == nil,但代码仍尝试dst.Addr.City = src.Addr.City
ulule/deepcopier 是目前最稳妥的生产级选择
它不依赖运行时反射遍历,而是通过编译期代码生成(需配合 go:generate)产出类型安全、零开销的拷贝逻辑,同时支持字段映射、忽略、条件复制。
- 必须确保源/目标结构体字段全部导出(首字母大写),否则字段不可见
- 安装:
go get github.com/ulule/deepcopier - 基础调用:
deecopier.Copy(&src).To(&dst),两个参数都必须是指针 - 字段重命名:
FullName string `deepcopier:"field:Name"`,注意 tag 值是源字段名 - 不支持未导出字段、
func、chan、unsafe.Pointer—— 这是 Go 语言限制,不是库缺陷
自己用反射写深拷贝时必须处理的边界
若因兼容性或轻量需求必须手写,核心是递归判断 Kind 并分支处理,不能只看 Type。例如 reflect.TypeOf((*int)(nil)).Elem() 才能得到 int 类型,否则会卡在 Ptr。
- 对
reflect.Ptr:先检查是否为nil,非 nil 才Elem()后递归;目标也要reflect.New()再Elem()赋值 - 对
reflect.Slice:用MakeSlice创建新切片,再逐元素递归拷贝(不能直接copy()) - 对
reflect.Map:用MakeMapWithSize创建,遍历MapKeys(),key/value 都要递归处理 - 对
interface{}:先reflect.ValueOf(v).Elem()取底层值,再按实际Kind分支,否则会误判为Interface类型死循环
真正难的不是“怎么递归”,而是统一处理 nil、空 map、未导出字段、自定义类型(如实现了 DeepCopy() interface{})这些情况;一旦漏掉,线上可能静默污染数据。ulule/deepcopier 把这些全收在生成代码里,比手写反射更可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











