Home > Mobile >  Assigning to type definition using reflection in Go
Assigning to type definition using reflection in Go

Time:08-17

I have setup a type called Provider that defines an integer:

type Provider int

func (enum *Provider) Scan(raw interface{}) error {
    *enum = Provider(int(raw))
}

If I create an object, Foo, with a Provider field, like this:

type Foo struct {
    Code Provider
    Value string
}

foo := &Foo {
    Code:  Provider(0),
    Value: "derp",
}

var scnr sql.Scanner
scannerType := reflect.TypeOf(&scnr).Elem()

tType := reflect.TypeOf(foo)
tField := tType.Field(0)
fmt.Printf("Field %s, of type %s, kind %s\n", 
    tField.Name, tField.Type, tField.Type.Kind())

When I run this code, I get that the field is of type Provider and its Kind is int. However, I cannot assign an int to a Provider using reflection because this will fail:


getInteger() interface{} {
    return 1
}

fValue := reflect.ValueOf(foo).Elem()
vField := fValue.Field(0)
vField.Set(reflect.ValueOf(getInteger())) // panic: reflect.Set: value of type int is not assignable to type Provider

The normal solution to this would be to do reflect.ValueOf(Provider(getInteger().(int))), however, this won't work because vField is set inside of a loop that iterates over a structure's fields and therefore will have a different type. Essentially, I would like a way to detect that vField is a definition of int (ie. Provider) rather than an int, itself; so I could use reflection to cast the integer value to Provider when I set it.

Is there a way I can make this work?

CodePudding user response:

The kind and type are not identical terms. Kind of both int and Provider is int because Provider has int as its underlying type. But the type Provider is not identical to int. What you have is a type definition, but not a type alias! Those are 2 different things. See Confused about behavior of type aliases for details.

The type of Foo.Code field is Provider, you can only assign a value of type Provider to it:

vField.Set(reflect.ValueOf(Provider(1)))

This works.

Note that reflection or not, you can't assign int to Foo.Code:

var i int = 3
var foo Foo
foo.Code = i // Error!

The above has compile time error:

error: cannot use i (variable of type int) as type Provider in assignment

What may be confusing is that using a constant works:

var foo Foo
foo.Code = 3 // Works!

This is because 3 is an untyped constant representable by a value of type Provider, so the constant will be converted to Provider.

CodePudding user response:

I ended up using the Convert function in conjunction with the Assignable function to do my check:


// Get value to assign and its type
vValue := reflect.ValueOf(getInteger())
tValue := vValue.Type()

// Check if the field can be assigned the value; if it can then do so
// Otherwise, check if a conversion can be assigned
vType := vField.Type()
if tValue.AssignableTo(vType) {
    vField.Set(vValue)
} else if vValue.CanConvert(vType) {
    vField.Set(vValue.Convert(vType))
}

This fixed my issue.

  • Related