go中赋值和copy只做浅拷贝,结构体含指针、map、slice等时会共享底层数据;深拷贝需用json/gob序列化或手动实现字段级复制,注意非导出字段、不可序列化类型及业务语义。

为什么 copy 和赋值不能用于结构体深拷贝
Go 的赋值和 copy 函数只做浅拷贝:如果结构体字段包含指针、map、slice、chan 或其他引用类型,拷贝后新旧结构体仍共享底层数据。修改一方的 map 元素,另一方会同步变化——这不是你想要的“深拷贝”。
常见错误现象:structA.FieldMap["key"] = "new" 后,structB.FieldMap["key"] 也变了;或者 append(structA.FieldSlice, x) 导致 structB.FieldSlice 长度意外增加。
- 只有所有字段都是基本类型(
int、string、bool)且不含嵌套结构体时,直接赋值才安全 -
copy只适用于 slice,对结构体无效;误用copy(dst, src)传结构体会编译报错 - 反射或
json序列化等通用方案有隐含限制(如非导出字段不可见、不支持函数/通道)
用 json.Marshal + json.Unmarshal 快速实现(适合简单场景)
这是最简路径,无需引入第三方库,适合配置结构体、DTO 等纯数据对象。但要注意它本质是“序列化再反序列化”,不是真正内存级拷贝。
示例:
func DeepCopyByJSON(src interface{}) (interface{}, error) {
data, err := json.Marshal(src)
if err != nil {
return nil, err
}
var dst interface{}
err = json.Unmarshal(data, &dst)
return dst, err
}
- 必须确保结构体字段全部可导出(首字母大写),否则
json包忽略它们 - 不支持
func、chan、unsafe.Pointer等类型,遇到会报json: unsupported type - 浮点数精度、时间格式(
time.Time默认转为字符串)、nilslice/map 的处理需额外注意
用 gob 编码实现更严格的深拷贝
gob 比 json 更贴近 Go 类型系统,能正确处理 time.Time、complex、自定义类型,且保留 nil slice/map 状态。但它要求所有类型必须可注册(通常默认支持),且性能略低于 json。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
示例:
func DeepCopyByGob(src interface{}) (interface{}, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
if err := enc.Encode(src); err != nil {
return nil, err
}
var dst interface{}
if err := dec.Decode(&dst); err != nil {
return nil, err
}
return dst, nil
}
- 同
json,非导出字段仍不可见,无法拷贝 - 如果结构体含未导出字段但你想保留其值,这条路走不通
- 对含
sync.Mutex等不可序列化字段的结构体,运行时报gob: cannot encode
手动实现字段级深拷贝(可控性最强)
当结构体字段类型明确、且需要完全控制行为(比如跳过某些字段、定制 slice 拷贝逻辑、处理私有字段),手写是最可靠的方式。没有魔法,但清晰、可测、无依赖。
示例(假设结构体 User 含 name string、tags []string、meta map[string]interface{}):
func (u *User) DeepCopy() *User {
if u == nil {
return nil
}
copy := &User{
Name: u.Name,
Tags: make([]string, len(u.Tags)),
Meta: make(map[string]interface{}),
}
copy.Tags = append(copy.Tags[:0], u.Tags...)
for k, v := range u.Meta {
copy.Meta[k] = v // 注意:v 是 interface{},若含 map/slice 仍需递归深拷贝
}
return copy
}
- 必须显式处理每个引用类型字段:
make+append或copyslice;make+ 循环赋值 map - 嵌套结构体字段要调用其自身的
DeepCopy方法,否则仍是浅拷贝 - 如果字段是接口类型(
io.Reader等),无法通用深拷贝,需按具体实现判断是否支持克隆
真正难的不是“怎么拷”,而是厘清哪些字段必须深拷、哪些可以共享、哪些根本不能拷——这取决于你的业务语义,而不是语言特性。










