Go Reflection: Creating Objects from Types — Part II (Composite Types)
source link: https://medium.com/kokster/go-reflection-creating-objects-from-types-part-ii-composite-types-69a0e8134f20
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Go Reflection: Creating Objects from Types — Part II (Composite Types)
This is part 2 of a 2 part blog series on Creating Objects from Types in Golang. This part discusses creation of composite types. The first post is available here.
Golang Gopher
In the previous blog post, I explained the concepts of type
and kind
in the go reflect package. In this post, I’ll be going deeper into the nomenclature used in Golang. This is because the terms type
and kind
carry more meaning for Composite types than Primitive types.
Types and Kinds
“type” is a term programmers use to describe metadata about data or functions in a program. The Go runtime and compiler has a different meaning for the word type
.
This can be understood through an example. Consider this declaration in a Go program
var Version string
When referring to this declaration, programmers would say Version
is a variable of type string
.
Consider the following example, where Version
is a composite type.
type VersionType stringvar Version VersionType
In this example, a new type called VersionType
gets created in the first line. In the second line, Version
is declared as a variable of type VersionType
. The type of its type (VersionType)
is string
. The name for this is kind
.
To summarize, the type
of Version
is VersionType
. The kind
of Version
is string
.
Type is the user defined metadata about data or function in a Go program. Kind is the compiler and runtime defined metadata about data or function in a Go program.
Kind
is used by the runtime and compiler to allocate the memory layout for a variable or to allocate the stack layout for a function.
Creating Composite Objects from Primitive Kinds
Creation of Composite Objects whose kind
is any of the following values is not different from creating Primitive Objects from Primitive Kinds.
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
String
UnsafePointer
Here’s an example of creating Version
from its type (VersionType
). Since Version
has a primitive kind
, it can be created by using the Zero value.
1 func CreatePrimitiveObjects(t reflect.Type) reflect.Value {
2 return reflect.Zero(t)
3 }
4
5 func extractVersionType(v reflect.Value) (VersionType, error) {
6 if v.Type().String() != "VersionType" {
7 return "", errors.New("invalid input")
8 }
9 return v.String(), nil
10 }
Note that the String()
method of a Type
variable returns the complete path and the name of the Type
. i.e. If VersionType
is defined in a different package called mypkg
, then the String()
method would return mypkg.VersionType
.
Creating Composite Objects from Composite Kinds
Composite Kinds are ones that include other Kinds. Map
, Struct
, Array
etc. are examples of Composite Kinds. Here is a the complete list of Composite Kinds.
Array
Chan
Func
Interface
Map
Ptr
Slice
Struct
Composite objects can be created using the Zero value just like Primitive objects. However, they cannot be initialized with an empty value of a particular type without additional processing. The subsequent sections provide more detail about initializing Composite Kinds.
Creating Composite Array Objects from their type signature
Empty Array Objects can be created by using the Zero value. The Zero value of an Array is an empty Array object. Here’s an example of creating an Array object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has a ArrayOf(Type, int)
function, that can be used to create Arrays containing elements of type Type
and specified length. Here’s an example showing its usage.
func CreateArray(t reflect.Type, length int) reflect.Value {
var arrayType reflect.Type
arrayType = reflect.ArrayOf(length, t)
return reflect.Zero(arrayType)
}
The type of the elements of the Array is decided by the incoming argument t
. The length of the Array is decided by the incoming argument length
. In order to extract the values into an array object, the best option using the reflect package is to process that array as an interface.
func extractArray(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Array {
return nil, errors.New("invalid input")
}
var array interface{}
array = v.Interface()
return array, nil
}
Note that the value’s Slice()
method can also be used to extract the value of the array. However, this methodology requires the conversion of the array type to an addressable array before its reflect.Value
is computed. It’s better to avoid this subtlety to keep your code base simple and readable by using the Interface()
method.
Creating Composite Channel Objects from their type signature
Empty channel Objects can be created using the Zero value. The Zero value of a channel is an empty channel object. Here’s an example of creating a channel object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has two functions to create a chan. The ChanOf
function can be used to create the type signature of the channel and then MakeChan(Type, int)
function can be used to allocate memory for the channel. Here’s an example showing its usage.
func CreateChan(t reflect.Type, buffer int) reflect.Value {
chanType := reflect.ChanOf(reflect.BothDir, t)
return reflect.MakeChan(chanType, buffer)
}
The type of the elements of the channel is decided by the incoming argument t
. The length of the channel is decided by the incoming argument buffer
. In order to extract the values into a channel object, the best option using the reflect package is to process that channel as an interface. The direction of the channel being created can be controlled by passing the direction as the first parameter to the call to ChanOf
. The possible values for ChanDir
are below.
SendDir
RecvDir
BothDir
BothDir
denotes that the channel can be written to and read from. SendDir
denotes that the channel can only be written to. RecvDir
denotes that the channel can only receive data.
func extractChan(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Chan {
return nil, errors.New("invalid input")
}
var ch interface{}
ch = v.Interface()
return ch, nil
}
Creating Composite Function Objects from their type signature
Function Objects cannot be created by using the Zero value.
The reflect package has two functions to create a function. The FuncOf
function can be used to create the type signature of the function and then MakeFunc(Type, func(args []Value) (results []Value)) Value
function can be used to allocate memory for the function. Here’s an example showing its usage.
func CreateFunc(
fType reflect.Type,
f func(args []reflect.Value)
(results []reflect.Value)
) (reflect.Value, error)
{
if fType.Kind() != reflect.Func {
return reflect.Value{}, errors.New("invalid input")
}
var ins, outs *[]reflect.Type
ins = new([]reflect.Type)
outs = new([]reflect.Type)
for i:=0; i<fType.NumIn(); i++ {
*ins = append(*ins, fType.In(i))
}
for i:=0; i<fType.NumOut(); i++ {
*outs = append(*outs, fType.Out(i))
}
var variadic bool
variadic = fType.IsVariadic() return AllocateStackFrame(*ins, *outs, variadic, f), nil
}func AllocateStackFrame(
ins []reflect.Type,
outs []reflect.Type,
variadic bool,
f func(args []reflect.Value)
(results []reflect.Value)
) reflect.Value
{
var funcType reflect.Type
funcType = reflect.FuncOf(ins, outs, variadic)
return reflect.MakeFunc(funcType, f)
}
CreateFunc
takes 2 arguments. The first is the type
of the function that you’d like to create, and the second is an actual function that implements the first argument’s type but the inputs and return values are converted to reflect.Value
objects. I’ve created this function to be a convenience method for users so that they do not have to re-invent how functions are created on the fly.
In order to use this to create functions, the type signature of the function needs to be defined. Here’s an example of a type signature
type fn func(int) (int)
The Type struct for the function signature above can be constructed using the method below.
1 var funcVar fn
2 var funcType reflect.Type
3 funcType = reflect.TypeOf(funcVar)
funcType
can be used as the first argument passed into CreateFunc
. A function that satisfies the type signature of funcType
is required to be passed as the second argument. The example below illustrates how:
func doubler(input int) (int) {
return input * 2
}
This function doubler
multiplies the input by 2. It takes in 1 argument of type int
and returns 1 argument of type int
. This function satisfies the fn
type, but does not satisfy the generic func type:
f func(args []reflect.Value) (results []reflect.Value)
To satisfy the generic func type, an equivalent version of the doubler
should be created that satisfies the generic func type. This is shown below.
func doublerReflect(args []reflect.Value) (result []reflect.Value) {
if len(args) != 1 {
panic(fmt.Sprintf("expected 1 arg, found %d", len(args)))
}
if args[0].Kind() != reflect.Int {
panic(fmt.Sprintf("expected 1 arg of kind int, found 1 args of kind", args[0].Kind()))
}
var intVal int64
intVal = args[0].Int()
var doubleIntVal int
doubleIntVal = doubler(int(intVal))
var returnValue reflect.Value
returnValue = reflect.ValueOf(doubleIntVal)
return []reflect.Value{returnValue}
}
doublerReflect
is functionally equivalent to doubler
and it satisfies the generic func type signature. i.e. It takes 1 argument, which is a slice of reflect.Value
objects, and returns 1 value, which is a slice of reflect.Value
objects. The input represents the input to the function, and return value represents the return value of the new function being made.
The function call to CreateFunc
can be made with funcType
and doublerReflect
as the arguments. The below example shows a function call being made from the newly created function.
func main() {
var funcVar fn
var funcType reflect.Type funcType = reflect.TypeOf(funcVar)
v, err := CreateFunc(funcType, doublerReflect)
if err != nil {
fmt.Printf("Error creating function %v\n", err)
}
input := 42
ins := []reflect.Value([]reflect.Value{reflect.ValueOf(input)})
outs := v.Call(ins)
for i := range outs {
fmt.Printf("%+v\n", outs[i].Interface())
}
}
// Output: 84
Creating Composite Map Objects from their type signature
Empty Map Objects can be created by using the Zero value. The Zero value of a Map is an empty Map object. Here’s an example of creating a Map object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has a MapOf(Type, Type)
function, that can be used to create Maps containing keys of the first argument’s type
and elements of the second argument’s type
. Here’s an example showing its usage.
func CreateMap(key , elem reflect.Type) reflect.Value {
var mapType reflect.Type
mapType = reflect.MapOf(key, elem)
return reflect.MakeMap(mapType)
}
In order to extract the values into an map object, the best possible option using the reflect package is to process that map as an interface.
func extractMap(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Map {
return nil, errors.New("invalid input")
}
var mapVal interface{}
mapVal = v.Interface()
return mapVal, nil
}
Note that maps can also be allocated by using MakeMapWithSize
. The steps to create maps using this method are exactly the same as above, but the MakeMap
call can be substituted with MakeMapWithSize
and a size parameter.
Creating Composite Ptr Objects from their type signature
Empty Ptr Objects can be created by using the Zero value. The Zero value of a Ptr is an nil
pointer object. Here’s an example of creating a Ptr object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has a PtrTo(Type)
function, that can be used to create Ptrs pointing to elements of type Type
. Here’s an example showing its usage.
func CreatePtr(t reflect.Type) reflect.Value {
var ptrType reflect.Type
ptrType = reflect.PtrTo(t)
return reflect.Zero(ptrType)
}
Note that the above functionality can also be achieved by using reflect.New(t)
. The technique illustrated above is functionally equivalent to using reflect.New(t)
.
The type of the element pointed to by the Ptr is decided by the incoming argument t
. In order to extract the values into an object, the best option using the reflect package is to first use the Indirect()
call or the Elem()
method to determine the value, and then process the value as an interface.
func extractElement(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Ptr {
return nil, errors.New("invalid input")
}
var elem reflect.Value
elem = v.Elem()
var elem interface{}
elem = v.Interface()
return elem, nil
}
Creating Composite Slice Objects from their type signature
Empty Slice Objects can be created by using the Zero value. The Zero value of a Slice is an empty Slice object. Here’s an example of creating an Slice object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has a SliceOf(Type)
function that can be used to create Slice containing elements of type Type
. Here’s an example showing its usage.
func CreateSlice(t reflect.Type) reflect.Value {
var sliceType reflect.Type
sliceType = reflect.SliceOf(length, t)
return reflect.Zero(sliceType)
}
The type of the elements of the Slice is decided by the incoming argument t
. In order to extract the values into a slice object, the best option using the reflect package is to process that slice as an interface.
func extractSlice(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Slice {
return nil, errors.New("invalid input")
}
var slice interface{}
slice = v.Interface()
return slice, nil
}
Creating Composite Struct Objects from their type signature
Empty Struct Objects can be created by using the Zero value. The Zero value of Struct is an empty Struct object. Here’s an example of creating an Struct object from its type signature
func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This function will create a reflect.Value
struct holding an empty object of any Composite type.
The reflect package has a StructOf([]reflect.StructFields)
function that can be used to create Struct containing fields of types defined in theStructField
object. Here’s an example showing its usage.
func CreateStruct(fields []reflect.StructField) reflect.Value {
var structType reflect.Type
structType = reflect.StructOf(fields)
return reflect.Zero(structType)
}
In order to extract the values into an array object, the best option using the reflect package is to process that struct as an interface.
func extractStruct(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Struct {
return nil, errors.New("invalid input")
}
var st interface{}
st = v.Interface()
return st, nil
}
Conclusion
This is a complete tutorial on creating any Go type on the fly during runtime using the reflect package. I’ve provided convenience methods for creating Func
type since it is more complex than the other types and can easily pollute your code base if not carefully designed.
Stay tuned for my next blog posts on converting any type to any other type in Golang! Following that, I’ll be explaining how to write a JIT (Just-in-Time) in Golang, and then about code generation using reflect
in Go.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK