
本文详解如何通过 go 反射机制自动将结构体字段转换为 []interface{} 指针切片,以适配 rows.scan() 要求;核心在于正确使用 reflect.value.addr().interface() 而非 .pointer()。
在 Go 中,database/sql.Rows.Scan() 方法要求传入的每个参数都必须是指向变量的指针(如 &user.Name, &user.Age),而非原始值或底层内存地址。初学者常误用 reflect.Value.Pointer(),以为它能直接生成 Scan 可接受的指针——但这是错误的:.Pointer() 返回的是 uintptr 类型的裸内存地址,属于不安全操作,无法被 Scan 识别为合法 Go 指针,因此触发 destination not a pointer panic。
正确做法是调用 reflect.Value.Addr().Interface():Addr() 创建一个指向字段的反射指针(reflect.Value 类型),而 Interface() 将其安全地转为 interface{},其中实际存储的是标准 Go 指针(如 *string, *int),完全符合 Scan 的类型契约。
以下是修正后的完整实现:
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
// StrutForScan 接收结构体指针,返回对应字段地址的 interface{} 切片
// ✅ 关键:使用 Addr().Interface(),而非 Addr().Pointer()
func StructForScan(u interface{}) []interface{} {
val := reflect.ValueOf(u)
if val.Kind() != reflect.Ptr {
panic("StructForScan: argument must be a pointer to struct")
}
val = val.Elem()
if val.Kind() != reflect.Struct {
panic("StructForScan: argument must point to a struct")
}
n := val.NumField()
result := make([]interface{}, n)
for i := 0; i < n; i++ {
field := val.Field(i)
// ✅ 正确:获取字段地址并转为可被 Scan 接受的 interface{}
result[i] = field.Addr().Interface()
}
return result
}
// 使用示例(模拟数据库查询流程)
func ExampleUsage() {
var user User
// 假设 rows 来自 db.Query("SELECT name, age FROM users")
// rows.Scan(StructForScan(&user)...) // ✅ 安全、有效
fmt.Printf("Scanning into: %+v\n", user)
}⚠️ 重要注意事项:
- 输入必须是结构体指针(如 &user),否则 reflect.ValueOf(u).Elem() 会 panic;
- 字段必须是可寻址的(即不能是结构体字面量或只读副本),这在传入 &user 时自然满足;
- 导出字段(首字母大写)才能被反射访问;私有字段将被跳过且不报错,需自行校验;
- Interface() 是唯一安全的“反射值 → 实际 Go 值”转换方式;Pointer() 属于 unsafe 范畴,仅用于极少数底层场景,绝不应用于 Scan。
总结:理解 reflect.Value.Interface() 与 .Pointer() 的本质区别,是掌握 Go 反射实践的关键分水岭。前者桥接反射世界与常规 Go 类型系统,后者仅暴露内存地址——在 database/sql 等标准库接口中,你永远需要前者。










