Go Reflection: Creating Objects from Types — Part I (Primitive Types)
This is part 1 of a 2 part blog series on Creating Objects from Types in Golang. This part discusses creation of primitive types.
The reflect package in Golang provides the necessary APIs to change the control flow of the program based on the type of the object being processed.
The reflect package provides two important structures - Type
and Value
.
Type
is a representation of any Go type. i.e. It can be used to encode any Go type (eg. int
, string
, bool
, myCustomType
etc.). Value
is a representation of any Go value. i.e. It can be used to encode and manipulate any Go value.
Types vs Kinds
Golang has a hidden, less known convention of making a distinction between type
and kind
. The distinction can be understood using an example. Consider this struct:
type example struct {
field1 type1
field2 type2
}
The type
of an object of this struct would beexample
. The kind
of that object would bestruct
. The kind
can be considered as the type
of a type
.
All structs in Golang are of the same
kind
, but not the sametype
Composite types likePointer
, Array
, Slice
, Map
etc. make this distinction between type
and kind
.
In contrast, Primitive types like int
, float
, string
etc. do not make the distinction between kind
andtype
. i.e. The kind
of an int
variable is int
. The type
of an int
variable is also int
.
Creating Objects from Types
In order to create an object from a type signature, both the type
and kind
of the object are necessary. Henceforth, when I use the term ‘type signature’, I mean Golang’s reflect.Type
object for that type.
Creating Primitive Objects from Primitive Types
Primitive Objects can be created from their type signatures by using their Zero values.
Zero value of a type is the value of the an uninitialized object of that type
Here’s a list of all the primitive types in Golang
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
String
UnsafePointer
Using reflect.Zero
function, objects of primitive types can be created
func CreatePrimitiveObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}
This will create an object of the desired type and return a reflect.Value
object corresponding to the underlying Zero value. In order to work with that object, its value needs to be extracted.
The value of an object can be extracted using the appropriate method for each of the primitive types.
Extracting Integer values
There are 5 Integer types in Golang:
Int
Int8
Int16
Int32
Int64
Int
type denotes Integers of the default size defined by the platform. The next 4 types are Integers of sizes(in bits) 8, 16, 32 and 64 respectively.
In order to extract each of the Integer types, the reflect.Value
object representing the Integer needs to be converted to the appropriate type.
Here’s how int32
should be extracted:
// Extract Int32
func extractInt32(v reflect.Value) (int32, error) {
if reflect.Kind() != reflect.Int32 {
return int32(0), errors.New("Invalid input")
}
var intVal int64
intVal = v.Int()
return int32(intVal), nil
}
It is important to note that reflect.Int()
always returns int64
. This is because int64
can encode all the other Integer types within it.
Here’s how the rest of the Integer types should be extracted:
// Extract Int64
func extractInt64(v reflect.Value) (int64, error) {
if reflect.Kind() != reflect.Int64 {
return int64(0), errors.New("Invalid input")
}
var intVal int64
intVal = v.Int()
return intVal, nil
}// Extract Int16
func extractInt16(v reflect.Value) (int16, error) {
if reflect.Kind() != reflect.Int16 {
return int16(0), errors.New("Invalid input")
}
var intVal int64
intVal = v.Int()
return int16(intVal), nil
}// Extract Int8
func extractInt8(v reflect.Value) (int8, error) {
if reflect.Kind() != reflect.Int8 {
return int8(0), errors.New("Invalid input")
}
var intVal int64
intVal = v.Int()
return int8(intVal), nil
}// Extract Int
func extractInt(v reflect.Value) (int, error) {
if reflect.Kind() != reflect.Int {
return int(0), errors.New("Invalid input")
}
var intVal int64
intVal = v.Int()
return int(intVal), nil
}
Extracting Boolean values
Boolean values are represented by the constant Bool
in the reflect package.
They can be extracted from reflect.Value
object using the Bool()
method:
// Extract Bool
func extractBool(v reflect.Value) (bool, error) {
if reflect.Kind() != reflect.Bool {
return false, errors.New("Invalid input")
}
return v.Bool(), nil
}
Extracting Unsigned Integers
There are 5 Unsigned Integer types in Golang:
Uint
Uint8
Uint16
Uint32
Uint64
Uint
type denotes Unsigned Integers of the default size defined by the platform. The next 4 types are Unsigned Integers of sizes(in bits) 8, 16, 32 and 64 respectively.
In order to extract each of the Unsigned Integer types, the reflect.Value
object representing the Unsigned Integer needs to be converted to the appropriate type.
Here’s how Uint32
should be extracted:
// Extract Uint32
func extractUint32(v reflect.Value) (uint32, error) {
if reflect.Kind() != reflect.Uint32 {
return uint32(0), errors.New("Invalid input")
}
var uintVal uint64
uintVal = v.Uint()
return uint32(uintVal), nil
}
It is important to note that reflect.Uint()
always returns uint64
. This is because uint64
can encode all the other Integer types within it.
Here’s how the rest of the Unsigned Integer types should be extracted
// Extract Uint64
func extractUint64(v reflect.Value) (uint64, error) {
if reflect.Kind() != reflect.Uint64 {
return uint64(0), errors.New("Invalid input")
}
var uintVal uint64
uintVal = v.Uint()
return uintVal, nil
}// Extract Uint16
func extractUint16(v reflect.Value) (uint16, error) {
if reflect.Kind() != reflect.Uint16 {
return uint16(0), errors.New("Invalid input")
}
var uintVal uint64
uintVal = v.Uint()
return uint16(uintVal), nil
}// Extract Uint8
func extractUint8(v reflect.Value) (uint8, error) {
if reflect.Kind() != reflect.Uint8 {
return uint8(0), errors.New("Invalid input")
}
var uintVal uint64
uintVal = v.Uint()
return uint8(uintVal), nil
}// Extract Uint
func extractUint(v reflect.Value) (uint, error) {
if reflect.Kind() != reflect.Uint {
return uint(0), errors.New("Invalid input")
}
var uintVal uint64
uintVal = v.Uint()
return uint(uintVal), nil
}
Extracting Floating Point Numbers
There are 2 Floating Point Number types in Golang:
Float32
Float64
Float32
type denotes Floating Point numbers that are 32 bits in size. Float64
type denotes Floating Point numbers that are 64 bits in size.
In order to extract each of the Floating Point Number types, the reflect.Value
object representing the Floating Point Number needs to be converted to the appropriate type.
Here’s how Float32
should be extracted:
// Extract Float32
func extractFloat32(v reflect.Value) (float32, error) {
if reflect.Kind() != reflect.Float32 {
return float32(0), errors.New("Invalid input")
}
var floatVal float64
floatVal = v.Float()
return float32(floatVal), nil
}
It is important to note that reflect.Float()
always returns float64
. This is because float64
can encode all the other Floating Point Number types within it.
Here’s how 64-bit Floating Point Number values should be extracted
// Extract Float64
func extractFloat64(v reflect.Value) (float64, error) {
if reflect.Kind() != reflect.Float64 {
return float64(0), errors.New("Invalid input")
}
var floatVal float64
floatVal = v.Float()
return floatVal, nil
}
Extracting Complex Values
There are 2 Complex types in Golang:
Complex64
Complex128
Complex64
type denotes Complex numbers that are 64bits in size. Complex128
type denotes Complex numbers that are 128 bits in size.
In order to extract each of the Complex types, the reflect.Value
object representing the Complex value needs to be converted to the appropriate type.
Here’s how Complex64
should be extracted:
// Extract Complex64
func extractComplex64(v reflect.Value) (complex64, error) {
if reflect.Kind() != reflect.Complex64 {
return complex64(0), errors.New("Invalid input")
}
var complexVal complex128
complexVal = v.Complex()
return complex64(complexVal), nil
}
It is important to note that reflect.Complex()
always returns complex128
. This is because complex128
can encode all the other Complex types within it.
Here’s how 128-bit Complex value should be extracted
// Extract Complex128
func extractComplex128(v reflect.Value) (complex128, error) {
if reflect.Kind() != reflect.Complex128 {
return complex128(0), errors.New("Invalid input")
}
var complexVal complex128
complexVal = v.Complex()
return complexVal, nil
}
Extracting string values
String values are represented by the constant String
in the reflect package.
They can be extracted from reflect.Value
object by using the String()
method of the object.
Here’s how String
should be extracted:
// Extract String
func extractString(v reflect.Value) (string, error) {
if reflect.Kind() != reflect.String {
return "", errors.New("Invalid input")
}
return v.String(), nil
}
Extracting Pointer values
There are 2 Pointer types in Golang:
Uintptr
UnsafePointer
Uintptr
and UnsafePointer
are just uint
values representing a virtual address in the process memory. It can represent the location of a variable or a function.
The difference between Uintptr
and UnsafePointer
is that Uintptr
is type-checked by the Go runtime, whereas UnsafePointer
is not. UnsafePointer
can be used to convert any Go type to any other Go type, given that their memory layouts are compatible. If this is something you would like to explore, please comment below, and I’ll write more on it.
Uintptr
and UnsafePointer
can be extracted from reflect.Value
object by using the Addr()
method and UnsafeAddr()
method respectively. Here’s an example showing how Uintptr
should be extracted
Uintptr should be extracted this way// Extract Uintptr
func extractUintptr(v reflect.Value) (uintptr, error) {
if reflect.Kind() != reflect.Uintptr {
return uintptr(0), errors.New("Invalid input")
}
var ptrVal uintptr
ptrVal = v.Addr()
return ptrVal, nil
}
Here’s how UnsafePointer
value should be extracted
// Extract UnsafePointer
func extractUnsafePointer(v reflect.Value) (unsafe.Pointer, error) {
if reflect.Kind() != reflect.UnsafePointer {
return unsafe.Pointer(0), errors.New("Invalid input")
}
var unsafeVal unsafe.Pointer
unsafeVal = unsafe.Pointer(v.UnsafeAddr())
return unsafeVal, nil
}
It is important to note that v.UnsafeAddr()
above returns a uintptr
value. It should be type converted in the same line, otherwise the unsafe.Pointer
value may not be pointing at the intended location.
What’s next
Please note that none of the methods of the reflect.Value
struct should be used without the check for their kind
, since it can lead to a panic
easily.
I’ll be writing about creating more complex types like struct
, pointer
, chan
, map
, slice
, array
etc. in my next blog post. Stay tuned!