go标准库无reflect.deepcopy,需手动实现:检查nil指针与可寻址性,用map[uintptr]bool检测循环引用,避免json方案的语义丢失与性能问题。

Go 里用 reflect.DeepCopy?不存在的
Go 标准库没有 reflect.DeepCopy,别被其他语言惯性带偏。想靠反射做深拷贝,得自己拼 reflect.Value 的递归逻辑,而且指针和循环引用不手动破,直接栈溢出或无限循环。
手动实现深拷贝时,怎么处理 *T 和 interface{}
反射拿到 reflect.Ptr 类型后,不能无脑 Elem() —— 如果是 nil 指针,Elem() panic;如果指向不可寻址值(比如字面量取地址),也 panic。必须先检查 IsValid() 和 CanInterface()。
- 对
*T:先IsNil(),是就 new 一个再递归拷贝;不是就Elem()后递归,再用Addr()包回去 - 对
interface{}:先Kind() == reflect.Interface,再用Elem()取底层值,但注意它可能还是 nil 或未初始化 - 遇到
unsafe.Pointer、func、chan、map(非 nil)等类型,得按需跳过或报错,它们没法安全深拷贝
检测并打断循环引用,靠的是 map[uintptr]bool 还是 map[reflect.Value]bool?
用 map[uintptr]bool 记录已访问对象地址更可靠。因为 reflect.Value 是只读快照,且对相同底层数据多次调用 reflect.ValueOf() 会生成不同实例,无法用它做键;而 uintptr 能稳定标识运行时对象地址(前提是值可寻址)。
- 每次进入结构体字段前,先
v.UnsafeAddr()得到地址,查 map 是否已存在 - 若存在,说明循环引用,返回错误或跳过该字段(取决于业务容忍度)
- 注意:
UnsafeAddr()对不可寻址值 panic,所以必须先CanAddr()判断 - 切片、map、指针值本身也要进 map,不只是结构体
为什么 json.Marshal/Unmarshal 不是真正的深拷贝方案
它看起来像深拷贝,但会丢掉很多东西:nil slice 变成空 slice、time.Time 序列化后精度可能丢失、自定义 UnmarshalJSON 方法可能副作用、不支持 unexported 字段、func/unsafe.Pointer 直接被忽略——这些都不是“拷贝”,是“重建”。
- 如果你的结构体含
sync.Mutex、http.Client、io.Reader等,json 方案直接失效 - 性能上,序列化+反序列化比反射递归慢一个数量级,尤其大结构体
- 真正需要深拷贝的场景,比如测试中复位状态、RPC 请求预处理、配置快照,都要求语义一致,不能妥协
循环引用检测和指针解引用这两步,漏掉任意一个,代码上线后大概率在某个边缘 case 里卡死或 panic,而不是报错——这才是最麻烦的。










