深拷贝不是“选不选”,而是“怎么控”:Go标准库不提供DeepCopy函数是设计约束,要求明确字段隔离策略;ulule/deepcopier编译期生成、零GC开销,但需导出字段、可写目标、双指针传参;json序列化仅作临时胶带,有字段丢失、类型限制与性能问题;手写Clone()才是生产底线,须按字段语义逐层决策。

深拷贝不是“选不选”,而是“怎么控”
Go 语言标准库不提供 DeepCopy 函数,这不是缺陷,是设计约束:它逼你回答「这个结构体里哪些字段必须隔离?哪些可以共享?哪些根本不能复制?」比如 sync.Mutex、http.Client、chan 或未导出字段,强行深拷贝会 panic 或静默失效。所以模块设计起点不是“实现通用拷贝”,而是明确边界——你要复制什么、不能复制什么、谁来负责生命周期。
ulule/deepcopier 是目前最稳的起点
它不依赖运行时反射,编译期生成逻辑,零 GC 开销,且类型安全。但必须注意几个硬性前提:
- 源结构体所有待拷贝字段必须导出(首字母大写),否则直接跳过
- 目标结构体对应字段必须可写(不能是只读字段或未导出字段)
- 传参必须是双指针:
deecopier.Copy(&src).To(&dst),传值或 nil 指针会 panic - 不支持
func、chan、unsafe.Pointer—— 这是 Go 语言限制,不是库的问题
字段映射用 struct tag:FullName string `deepcopier:"field:Name"`,忽略字段加 `deepcopier:"-"`。适合 DTO 转换、配置快照等字段语义清晰的场景。
json.Marshal/Unmarshal 只能当临时胶带用
它看起来最简单,但行为边界极窄:
立即学习“go语言免费学习笔记(深入)”;
- 仅处理导出字段,
password string这类私有字段直接消失 - 遇到
chan int或func()会 panic:json: unsupported type: chan int -
time.Time变成字符串,反解后丢失纳秒精度和时区信息 - 空
map或slice反序列化后变成nil,不是空容器 - 性能开销大,高频调用(如每毫秒一次)会导致明显 GC 压力
示例代码必须显式传指针:err := json.Unmarshal(data, &dst),传值无效;且 dst 类型必须与 src 兼容,否则反序列化失败。
手写 Clone() 方法才是生产环境的底线
当你控制结构体定义、字段稳定、含不可序列化类型(如 sync.RWMutex、*sql.DB)时,必须自己写。关键不是“全递归”,而是按字段语义决策:
- 基础类型(
int、string)直接赋值 -
[]T:用append([]T(nil), src.Slice...)或make+copy -
map[K]V:dst.Map = make(map[K]V); for k, v := range src.Map { dst.Map[k] = v }(若 V 是结构体,需递归调用其Clone()) -
*T:判断是否为 nil,非 nil 则dst.Ptr = src.Ptr.Clone() -
sync.Mutex等同步原语:不能拷贝,应共享指针或重置(dst.Mutex = sync.Mutex{})
最容易被忽略的是嵌套结构体的 Clone() 是否被调用——漏掉一层,就等于在某处留了浅拷贝漏洞。


















