
本文详解如何通过 go 反射机制自动为结构体每个字段生成 `*interface{}` 类型的地址引用,以适配 `rows.scan()` 要求的指针参数列表,避免手动书写冗长的 `&u.name, &u.age` 等表达式。
在 Go 中,database/sql.Rows.Scan() 方法要求传入的每个参数都必须是指向变量的有效指针(如 *string, *int),而不能是原始值或不安全的整数地址。初学者常误用 reflect.Value.Pointer(),以为它返回的是可直接用于 Scan 的 Go 指针——但事实并非如此。
reflect.Value.Pointer() 返回的是一个 uintptr(即内存地址的整数表示),它不是 Go 语言意义上的指针类型,无法被 Scan 识别,强行使用会导致 destination not a pointer panic。真正需要的是 reflect.Value.Addr().Interface():它将字段地址转换为对应类型的接口值(例如 *string 或 *int),该接口值内部包裹着合法的 Go 指针,完全符合 Scan 的类型契约。
以下是修正后的完整实现:
import "reflect"
type User struct {
Name string
Age int
}
func StructForScan(u interface{}) []interface{} {
val := reflect.ValueOf(u).Elem() // u 必须是指向结构体的指针,如 &User{}
if val.Kind() != reflect.Struct {
panic("StructForScan: argument must be a pointer to a struct")
}
v := make([]interface{}, val.NumField())
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if !field.CanAddr() {
panic("StructForScan: cannot take address of unaddressable field at index " + string(rune('0'+i)))
}
v[i] = field.Addr().Interface() // ✅ 正确:返回 *T 类型的 interface{}
}
return v
}使用示例:
func ListUsers(db *sql.DB) {
rows, err := db.Query("SELECT name, age FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
var user User
for rows.Next() {
// 自动展开为 rows.Scan(&user.Name, &user.Age)
err := rows.Scan(StructForScan(&user)...)
if err != nil {
panic(err)
}
fmt.Printf("Name: %s, Age: %d\n", user.Name, user.Age)
}
}⚠️ 重要注意事项:
- 输入 u 必须是指向结构体的指针(如 &user),否则 reflect.ValueOf(u).Elem() 会 panic;
- 所有需扫描的字段必须是可寻址的(即不能是嵌入的未导出匿名字段、或位于不可变上下文如切片字面量中);
- 字段必须导出(首字母大写),否则 reflect 无法访问其值(尽管 Scan 本身不要求导出,但反射操作需要);
- StructForScan 不处理 SQL NULL 值;如需支持,应改用 sql.NullString 等类型,并确保结构体字段类型匹配。
总结:Addr().Interface() 是反射中获取“可用 Go 指针”的标准方式;而 Pointer() 仅用于底层内存操作(配合 unsafe),绝不应用于 database/sql 接口。掌握这一区别,是理解 Go 反射与类型系统协同工作的关键一步。










