Go运行时动态设置变量类型及Gorm First()调用Panic问题排查
问题:Gorm First() 调用触发 reflect panic 的原因及通用查询函数实现
我编写了如下使用Gorm First()方法的Go函数:
func (r *SqlRepository) Get(id int, inventory any) (any, error) { result := db.First(&inventory, id) if result.Error != nil { var s struct{} return s, result.Error } else { return inventory, nil } }
调用该函数时传入int类型ID和自定义结构体Get(1, model.InventoryToys{}),触发panic:
panic: reflect: reflect.Value.Set using unaddressable value
我理解any表示可接受任意类型,传入model.InventoryToys{}后inventory应为此类型,但实际并非如此,请问我哪里理解有误?
背景:我有多个库存表对应不同结构体,希望通过一个通用Get函数避免重复代码,实现运行时动态指定类型让Gorm查询对应表。
解答
核心问题分析
- 不可寻址的值拷贝:你传入的
model.InventoryToys{}是值类型,赋值给any接口变量后,接口内部存储的是该值的拷贝,这个拷贝是不可寻址的。&inventory取的是any变量本身的地址,而非结构体实例的地址,Gorm无法通过这个地址完成反射赋值。 - Gorm的参数要求:Gorm的
First方法要求第一个参数必须是指针类型,只有这样它才能将查询结果写入指针指向的内存空间。传入非指针时,反射无法修改不可寻址的值,直接触发panic。
修正方案
方案一:调用时传入指针(推荐)
直接修改调用方式,传入结构体指针,同时调整函数内部逻辑:
func (r *SqlRepository) Get(id int, inventory any) (any, error) { result := db.First(inventory, id) // inventory已是指针,无需再取地址 if result.Error != nil { return nil, result.Error // 返回nil比空结构体更合理 } return inventory, nil }
调用示例:
repo.Get(1, &model.InventoryToys{})
方案二:函数内部自动创建指针(适配原调用方式)
如果不想修改调用方式,可通过反射基于传入的类型创建可寻址的指针实例:
import "reflect" func (r *SqlRepository) Get(id int, inventory any) (any, error) { // 基于传入的类型创建指针实例 targetType := reflect.TypeOf(inventory) targetPtr := reflect.New(targetType) result := db.First(targetPtr.Interface(), id) if result.Error != nil { return nil, result.Error } // 返回实例的值(与调用方传入的类型一致) return targetPtr.Elem().Interface(), nil }
调用方式保持不变:
repo.Get(1, model.InventoryToys{})
对any的理解纠正
any确实可以接收任意类型,但值类型存入接口后,接口内部存储的是值的拷贝,这个拷贝处于接口的封装结构中,无法被直接寻址。你之前误以为&inventory是结构体实例的地址,实际上它是any接口变量的地址,Gorm无法通过这个地址操作到真正的结构体数据。只有传入指针类型,或者在函数内部通过反射创建可寻址的实例,才能满足Gorm的赋值要求。
内容的提问来源于stack exchange,提问作者mevoker659
相关产品推荐
相关产品推荐

