不能直接用=或copy(),因为它们都是浅拷贝:值类型字段完整复制,引用类型字段(如[]string、map、*T)仅复制地址,导致副本与原对象共享底层数据;修改副本会意外影响原始数据。

为什么不能直接用 = 或 copy()
因为它们都是浅拷贝。= 对结构体赋值时,若字段含 []string、map[string]int、*T 或 interface{},副本和原对象共享底层数据。改 dst.Slice[0],src.Slice[0] 也跟着变;copy(dstSlice, srcSlice) 只复制切片头,元素若为指针或 map,照样共用内存。常见错误现象是:缓存里取出来的结构体,一修改就污染了原始缓存项。
ulule/deepcopier 是最稳的编译期方案
它不依赖运行时反射,零性能开销,类型安全,且支持字段映射、忽略、重命名。但必须满足两个前提:src 和 dst 的字段都得导出(首字母大写),且目标结构体字段可写。
- 安装:
go get github.com/ulule/deepcopier - 基础用法:
deecopier.Copy(&src).To(&dst)—— 注意传指针 - 字段映射:
type DTO struct { Name string `deepcopier:"field:FullName"` } - 忽略字段:
IgnoredField string `deepcopier:"-"` - 嵌套结构体自动递归处理,无需额外配置
它生成的是纯 Go 代码,IDE 能跳转、能调试,不像反射方案那样黑盒。
用 json.Marshal/Unmarshal 快速但限制多
适合临时调试或纯数据 DTO,但生产环境容易踩坑:
立即学习“go语言免费学习笔记(深入)”;
- 只处理导出字段,未导出字段(小写字母开头)直接丢弃
- 含
func、chan、unsafe.Pointer会 panic -
time.Time变成字符串再解析,可能丢失纳秒精度或时区信息 -
map[interface{}]interface{}这类无法 JSON 编码的类型直接失败 - 性能差,每次都要序列化+反序列化,高频调用不可接受
示例:json.Unmarshal(json.Marshal(src), &dst) 看似一行,实则隐含大量内存分配和 GC 压力。
自己写 Clone() 方法最可控
对关键业务结构体,显式实现 Clone() 是 Go 哲学推荐的做法。它完全避开泛型/反射/序列化的不确定性,字段行为一目了然。
- 基本字段直接赋值:
Name: src.Name, Age: src.Age - 切片要
make+copy:Hobbies: append([]string(nil), src.Hobbies...) - map 要
make+ 遍历复制:Roles: func(m map[string]bool) map[string]bool { r := make(map[string]bool); for k, v := range m { r[k] = v }; return r }(src.Roles) - 嵌套结构体调用其
Clone()方法,指针需判空再解引用 - 如果结构体含
sync.Mutex等不可拷贝类型,Clone()里应初始化新实例,而非复制锁状态
真正难的不是写 Clone,而是想清楚哪些字段该深拷,哪些该共享(比如 logger 实例通常不该复制)。这点没有通用工具能替你判断。


















