反映在函数参数中传递给接口{}的struct - Golang

时间:2018-03-13 01:02:20

标签: go reflection

我将struct传递给interface{}函数。然后我在内部使用reflect来获取结构属性。这是代码:

func (db *DB) Migrate(domain ...interface{}) {
    // statement := "CREATE TABLE IF NOT EXISTS %s (%s, %s, %s, %s, %s)"
    for _,i := range domain {
        params := BindStruct(&i)
        statement := CreateStatement("create", len(params))
        _,err := db.Exec(fmt.Sprintf(statement, params...))
        if err != nil {
            log.Fatal("Error migrating database schema - ", err)
            break
        }
    }
} 

func BindStruct(domain interface{}) (params []interface{}) {
    tableName := reflect.TypeOf(domain).Elem().Name()
    params = append(params, tableName)

    val := reflect.ValueOf(domain).Elem()
    for i:=0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}

我在for i:=0; i < val.NumField(); i++ {函数中的BindStruct行上出错了。错误消息是:

  

恐慌:反映:在界面值

上调用reflect.Value.NumField

如果我从迁移功能中的& params := BindStruct(&i)中删除params := BindStruct(i),则会出现此错误:

  

恐慌:运行时错误:无效的内存地址或无指针

     

dereference [signal SIGSEGV:segmentation violation code = 0x1 addr = 0x0 pc = 0x4ff457]

是什么给出了?

2 个答案:

答案 0 :(得分:5)

当您调用BindStruct(&i)时,您正在传递指向接口的指针。所以这一行:

val := reflect.ValueOf(domain).Elem()

将val设置为表示接口的reflect.Value,因为reflect.ValueOf(domain)获取指针然后.Elem()解析接口 - 这会导致您的第一个错误,因为它确实是一个接口值(并且它们不会没有字段):

  

恐慌:反映:在界面值

上调用reflect.Value.NumField

因此,调用params := BindStruct(i)总是正确的,因为你需要传递实际的接口,而不是指向它的指针。

您不清楚传递给Migrate()的基础数据类型是什么 - 它们是值还是指针?重要的是要知道,例如,使用反射来检查struct标签,我们需要返回结构值类型,因此链接:

  

界面 - &gt; (指针?) - &gt;价值 - &gt;型

我怀疑你使用的值好像接口是一个值,然后是行:

val := reflect.ValueOf(domain).Elem()
由于reflect.ValueOf(domain)会解析该值,然后.Elem()会尝试取消某个值,因此预计会出现恐慌。

为了安全起见,我们将检查传入值的Kind()以确保我们有一个结构:

https://play.golang.org/p/6lPOwTd1Q0O

func BindStruct(domain interface{}) (params []interface{}) {

    val := reflect.ValueOf(domain) // could be any underlying type

    // if its a pointer, resolve its value
    if val.Kind() == reflect.Ptr {
        val = reflect.Indirect(val)
    }

    // should double check we now have a struct (could still be anything)
    if val.Kind() != reflect.Struct {
         log.Fatal("unexpected type")
    }

    // now we grab our values as before (note: I assume table name should come from the struct type)
    structType := val.Type()  
    tableName := structType.Name()
    params = append(params, tableName)

    for i:=0; i < structType.NumField(); i++ {
        field := structType.Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}

答案 1 :(得分:1)

删除&amp;和Elem(),如下:

func (db *DB) Migrate(domain ...interface{}) {
    // statement := "CREATE TABLE IF NOT EXISTS %s (%s, %s, %s, %s, %s)"
    for _,i := range domain {
        params := BindStruct(i)
        statement := CreateStatement("create", len(params))
        _,err := db.Exec(fmt.Sprintf(statement, params...))
        if err != nil {
            log.Fatal("Error migrating database schema - ", err)
            break
        }
    }
} 

func BindStruct(domain interface{}) (params []interface{}) {
    tableName := reflect.TypeOf(domain).Name()
    params = append(params, tableName)

    val := reflect.ValueOf(domain)
    for i:=0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}