Go实现通用Protobuf结构体持久化Postgres的空指针问题求助
问题:泛型实现Protobuf结构体的PostgreSQL序列化存储失败
希望将Protobuf结构体以字节数组形式存储到PostgreSQL,为避免为每个新类型定义包装结构体,尝试用泛型实现Scan()和Value()方法,但运行时出现runtime error: invalid memory address or nil pointer dereference错误。
使用的代码如下:
type SqlProto[T proto.Message] struct { Valid bool Proto T } func (d SqlProto[T]) Scan(value interface{}) error { if value == nil { d.Valid = false return nil } bytes, ok := value.([]byte) if !ok { return fmt.Errorf("invalid value for %T: %v", value, value) } d.Proto = *new(T) err := proto.Unmarshal(bytes, d.Proto) if err != nil { return fmt.Errorf("failed to parse %T: %w", value, err) } d.Valid = true return nil } func (d SqlProto[T]) Value() (driver.Value, error) { if !d.Valid { return nil, nil } return proto.Marshal(d.Proto) }
错误核心:new(T)返回的是指向T类型的指针,而T本身属于proto.Message(本质是指针类型),因此*new(T)得到的是nil指针,proto.Unmarshal无法对nil指针完成反序列化;同时原代码Scan方法用值接收者,修改的是结构体副本,无法同步到原实例。
解决方案
关键修改点
- 将
Scan方法的接收者改为指针类型,确保修改能作用到原结构体实例 - 通过反射正确创建
T类型的实例,避免nil指针问题
修正后的代码
import ( "database/sql/driver" "fmt" "reflect" "google.golang.org/protobuf/proto" ) type SqlProto[T proto.Message] struct { Valid bool Proto T } // 改为指针接收者,确保修改同步到原实例 func (d *SqlProto[T]) Scan(value interface{}) error { if value == nil { d.Valid = false d.Proto = nil return nil } bytes, ok := value.([]byte) if !ok { return fmt.Errorf("invalid value type: expected []byte, got %T", value) } // 创建T类型的有效实例:T是*MyProto类型,先获取底层结构体类型,再创建指针 msgType := reflect.TypeOf(*new(T)).Elem() msg := reflect.New(msgType).Interface().(T) err := proto.Unmarshal(bytes, msg) if err != nil { return fmt.Errorf("failed to unmarshal protobuf: %w", err) } d.Proto = msg d.Valid = true return nil } // 改为指针接收者保持一致性,同时增加空值判断 func (d *SqlProto[T]) Value() (driver.Value, error) { if !d.Valid || d.Proto == nil { return nil, nil } return proto.Marshal(d.Proto) }
代码说明
- 指针接收者:
Scan和Value方法使用*SqlProto[T]作为接收者,确保对结构体字段的修改直接作用到原实例,而非副本。 - 实例创建:通过
reflect.TypeOf(*new(T)).Elem()获取Protobuf消息的底层结构体类型,再用reflect.New创建对应指针实例(即T类型),确保proto.Unmarshal有非nil的实例可以写入数据。 - 边界处理:
Value方法增加d.Proto == nil的判断,避免调用proto.Marshal时触发nil指针错误。
内容的提问来源于stack exchange,提问作者Jacopo Rufini
相关产品推荐
相关产品推荐

