reflect.Copy不适用于结构体字段赋值,因其仅支持slice和array内存拷贝,对struct会panic;结构体赋值需遍历字段、检查可设置性、类型兼容性及导出性,并传指针确保可寻址。

不能直接“结构体之间自动赋值”,必须明确源和目标的可寻址性、字段导出性、类型兼容性;反射只是工具,不是魔法。
为什么 reflect.Copy 不适用于结构体间字段赋值
很多人第一反应是查 reflect.Copy,但它只支持 slice 和 array 之间的内存拷贝,对 struct 完全无效——调用会 panic 报 invalid argument to reflect.Copy。结构体字段赋值本质是「遍历 + 类型匹配 + 可设置性检查 + Set* 调用」,不是内存平移。
-
reflect.Copy的文档明确限定其参数必须是reflect.Slice或reflect.Array类型 - struct 是聚合类型,字段布局、对齐、嵌入关系都需逐层解析,无法靠底层拷贝解决
- 即使两个 struct 字段名/类型完全一致,若目标字段未导出(小写开头),
reflect.Value.Set仍会 panic
必须传指针:否则 reflect.Value.CanSet() 恒为 false
这是最常踩的坑:传入结构体值(如 User{})而非指针(如 &User{}),会导致所有字段的 CanSet() 返回 false,后续 Set* 全部失败。
- 原因:Go 反射要求目标值必须「可寻址」(addressable),只有指针解引用后得到的
reflect.Value才满足条件 - 验证方式:
v := reflect.ValueOf(u); fmt.Println(v.CanAddr(), v.CanSet())—— 值类型输出false false,指针类型解引用后才是true true - 安全写法:函数入口强制检查
v.Kind() == reflect.Ptr && v.Elem().CanAddr(),再调用v.Elem()
字段匹配逻辑:名称优先,但需处理嵌入字段与大小写敏感
自动赋值不是靠标签(如 json:"name")驱动,而是默认按字段名严格匹配。但 Go 的嵌入机制会让事情变复杂。
- 源结构体中的嵌入字段(如
type A struct{ B })不会自动映射到目标结构体的同名嵌入字段,必须显式递归遍历v.Field(i).Type()判断是否为reflect.Struct并进入下一层 - 字段名区分大小写:源
Name可匹配目标Name,但无法匹配name(未导出)或NAME(类型不兼容) - 若需标签驱动(如按
mapstructure:"user_id"匹配),必须手动解析reflect.StructField.Tag.Get("mapstructure"),不能依赖默认行为
类型转换失败时不要 panic,要降级处理
反射赋值最脆弱的一环是类型不兼容:比如把 string 赋给 int 字段。硬编码 SetInt/SetString 会 panic,而用 Convert 又受限于 Go 的合法转换规则(如 string → int 不被允许)。
- 推荐策略:先用
src.Convert(dst.Type())尝试,捕获panic后 fallback 到字符串解析(如strconv.Atoi)或忽略 - 特别注意时间、布尔、数字切片等常见类型:它们没有内置
Convert路径,必须单独分支处理 - 永远检查
dst.CanSet() && src.IsValid() && src.Type().AssignableTo(dst.Type())三重条件,缺一不可
真正难的不是写反射循环,而是决定「哪些字段该跳过」「类型不匹配时该报错还是静默」「嵌入层级深了怎么避免无限递归」——这些业务逻辑边界,反射本身从不帮你判断。


















