反映传递给interface {}函数参数的结构

I pass struct into a function as interface{}. Then inside I work with it using reflect to get the struct attributes. Here's the code:

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
}

I got an error on the line for i:=0; i < val.NumField(); i++ { in BindStruct function. The error message is:

panic: reflect: call of reflect.Value.NumField on interface Value

If I remove & from params := BindStruct(&i) becoming params := BindStruct(i) in Migrate function, I get this error:

panic: runtime error: invalid memory address or nil pointer

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

What gives?

When you call BindStruct(&i) you are passing a pointer to an interface. so this line:

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

will set val to a reflect.Value representing your interface because reflect.ValueOf(domain) gets a pointer then .Elem() resolves the interface - which results in your first error as it is indeed an interface value (and they don't have fields):

panic: reflect: call of reflect.Value.NumField on interface Value

So, calling params := BindStruct(i) would always be correct as you need to pass the actual interface in not a pointer to it.

You don't make clear what the underlying data types being passed into Migrate() are - are they values or pointers? It's important to know, e.g to inspect struct tags using reflection we need to get back to the struct values type, so the chain goes:

interface -> (pointer ?) -> value -> type

I suspect you are using values as if interface was a value then the line:

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

would be expected to panic since reflect.ValueOf(domain) would resolve the value and then .Elem() would try to de-refrence a value.

To be on the safe side we will check the Kind() of the incoming value to make sure we have a struct:

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
}

Remove & and Elem(), As follow:

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
}