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.
Golang Gopher

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 typeand 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 same type

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!