Golang Type Casting or Conversion - Detailed Guide

Introduction

A programming process to convert one type of data into another is called type casting or type conversion in Golang.

The creators of Go describe this process on their website as “type conversion”.

There are several advantages to type casting or conversion in Golang, such as arithmetic operations, managing memory, interacting with API, avoiding runtime errors, and helping to understand the flow and logic of the code.

You will find out everything you need to know about type conversion or type casting in Golang on this blog.

Prerequisite

Before going deeper into type conversion, let’s refresh our understanding of data types.

1. Fundamental Data Types

1.1 Boolean

Boolean exists in two type only i.e. True or False

1.2 Numeric Types

- Integer Types

Signed integers: int, int8, int16, int32, int64

Unsigned integers: uint, uint8 (alias byte), uint16, uint32, uint64, uintptr

- Floating Point Types

float32, float64 

- Complex Types

complex64 (two float32 values), complex128 (two float64 values)

1.3 String

represents a sequence of UTF-8 encoded characters.

2. Composite Data Types

2.1 Array

A fixed-size collection of elements of single type. Example: 

integerArray := [3]int{1, 2, 3}

2.2 Slice

Dynamic-size, flexible view into arrays. Example: 

integerSlice := []int{1, 2, 3}

2.3 Map

Collection of key-value pairs. Example:

fruitMap := map[string]int{"apple": 1, "banana": 2}

2.4 Struct

complex64 (two float32 values), complex128 (two float64 values)

type Person struct {
   Name string
   Age  int
}

3. Special Data Types

3.1 Interface Type

An interface type consists of methods. A value of interface type can contain any value that implements those methods.

type Animal interface {
   Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
    return "Woof"
}

3.2 Empty Interface

The interface type, which consists of zero methods, is an empty interface:

An empty interface can contain any data type.

var emptyInterface interface{}

3.3 Pointer

Holds the memory address of a variable. Example

var p *int

3.4 Channel

An interface type consists of methods. A value of interface type can contain any value that implements those methods.

ch := make(chan int)

Implicit or Explicit type casting or type conversion

In the Go programming language, there is no support for implicit type conversion. Implicit type conversion automatically changes data from one type to another. However, in explicit type conversion, developers have to specify the data type they want to convert the data to.

Explicit type conversion example :

var number1 float64 = 3.14
var number2 int = int(x) // explicit type conversion from float64 to int

Type conversion syntax :

The statement T(v) changes the datatype v to the type T.

Limitations of explicit type conversion

Explicit type conversion in Golang sometimes leads to unexpected results, especially when converting between different numeric types. Here are some examples:

1. Truncation

When you convert (float64 or float32) datatype to another datatype(int), the decimal part of the number is ignored.

For Example

var number1 float64 = 20.99
var number2 int = int(number1) // y will be 20, not 21

2. Loss of precision

When you convert a floating-point number to an integer, the precision of the number may be lost. For example:

var number1 float64 = 0.8
var number2 int = int(number1) // y will be 0, not 1

3. Overflow or underflow

When you convert a number to a smaller numeric type, the value may overflow or underflow. For example:

var a int16 = 129
var b int8 = int8(a) //output: -127

4. Panic

A panic may occur when you convert a value to an invalid type. For example:

var x int = 1
var y string = string(x)
//string function only supports 8-bit byte characters not suitable for UTF-8 text

int16 can contain values from -32768 to 32767. int8 can contain values from -128 to 127.
whereas 129 is outside the int8 range, it wraps around due to overflow. So, b becomes -127 (because 129 – 256 = -127).

Convert integer to string

You can use the built-in package named “strconv” in the Go programming language. The “strconv” package has a function called Itoa() that converts an integer to a string. Here’s how it works or for a detailed program

func main() {
   var secondsInOneMinute int = 60
   var secondsInOneMinuteString string = strconv.Itoa(secondsInOneMinute)
   message := "One minute consists of " + secondsInOneMinuteString + " seconds"
   fmt.Println(message)
} // output: One minute consists of 60 seconds

You can also use the built-in package “fmt” which has a function called “Sprintf” for converting int to string. Here’s how it works:

func main() {
   number := 123
   messageString := "The number value is " + fmt.Sprintf("%d", number)
   fmt.Println(messageString)
} // output: The number value is 123

Convert string to integer

You can use the built-in package named “strconv” in the Go programming language. The “strconv” package has a function called “Atoi()” that converts an integer to a string. Here’s how it works or for a detailed program

func main() {
   var mobileNumberString string = "1234567890"
   mobileNumber, err := strconv.Atoi(mobileNumberString)
   if err != nil {
       fmt.Println("Error converting string to int:", err)
       return
   }
   fmt.Println(mobileNumber)
}//output : 1234567890

There is no function in fmt package to convert a string to an integer

Convert floating-point-number to string

You can use the built-in package named “strconv” in the Go programming language. The“strconv” package has a function called “FormatFloat()” that converts floating-point-number to string. Here’s how it works for float64 or for a detailed program

func main() {
   var percentageValue float64 = 12.5
   var percentageValueString string = strconv.FormatFloat(percentageValue, 'f', 2, 64)
   message := percentageValueString +" percentage is equal to 1/8 fraction" 
   fmt.Println(message)
}//output: 12.5 percentage is equal to 1/8 fraction

You can also use the built-in package “fmt” which has a function called “Sprintf” for converting floating point numbers to string. Here’s how it works:

func main() {
   num := 12.34
   messageString := "The number value is " fmt.Sprintf("%f", num)
   fmt.Println(messageString)
} //Output: The number value is 12.34

Convert a string to floating-point-number

You can use the built-in package named  “strconv” in the Go programming language. The  “strconv” package has a function called “ParseFloat()” that converts floating-point-number to string. Here’s how it works for float64 or for a detailed program

func main() {
   var circleOutsideDiameterString string = "30.35"
 circleOutsideDiameter, err := strconv.ParseFloat(circleOutsideDiameterString, 64)
   if err != nil {
       fmt.Println("Error converting string to float64:", err)
       return
   }
   fmt.Println(circleOutsideDiameter)
}// output : 	30.3

There is no function in”fmt” package to convert a string to a floating point number

Convert byte ([]byte{}) to string

To convert the string to a byte slice, or for a detailed program, you can take advantage of String().

byteSlice := []byte{71, 111, 112, 104, 101, 114, 115, 33} // Represents "Gophers!"
str := string(byteSlice)
fmt.Println(str) // Output: Gophers!

Convert string to byte ([]byte{})

Example or for a detailed program

str := "Gophers!"
byteSlice := []byte(str)
fmt.Println(byteSlice)
//output : [71 111 112 104 101 114 115 33]

Type conversion in array, slice, maps and structs

In Golang, type conversion for simple types like integers and floats is straightforward. However, converting more complex types such as the arrays, slices, maps, and structs requires a different approach since Golang does not support direct type conversion for these types. Instead, you have to manually convert each element or field. Here’s an overview of how you can perform these conversions:

1. Arrays & Slices

To convert between arrays of different types, you need to create a new array and manually copy and convert each element or for a detailed program

func main() {
   var intArray = [3]int{1000, 2000, 3000}
   var floatArray [3]float64

   for i, v := range intArray {
       floatArray[i] = float64(v)
   }
   
   fmt.Println("Converted float array:", floatArray)
}

For slice,

   intSlice := []int{1000, 2000, 3000}
   floatSlice := make([]float64, len(intSlice))

2. Maps

For maps, you need to create a new map with the desired key and value types and then copy and convert each key-value pair.

func main() {
   intMap := map[string]int{"Number1": 1000, "Number2": 2000, "Number3": 3000}
   floatMap := make(map[string]float64)
   for k, v := range intMap {
       floatMap[k] = float64(v)
   }
   fmt.Println("Converted float map:", floatMap)
}

3. Structs

For structs, you need to define the target struct type and manually copy and convert each field.

type IntStruct struct {
   Number1 int
   Number2 int
}
type FloatStruct struct {
   Number1 float64
   Number2 float64
}

func main() {
   intStruct := IntStruct{Number1: 121, Number2: 144}
   floatStruct := FloatStruct{
       Number1: float64(intStruct.Number1),
       Number2: float64(intStruct.Number2),
   }
   fmt.Println("Converted float struct:", floatStruct)
}

Alternative to type casting when data type is interface

1. Type assertion in interface

When you use interfaces, you might need to change the interface to a specific type. This is called type assertion.

Type assertion is used to retrieve the dynamic value stored in an interface variable. Here’s an example of empty interface or for a detailed program

  var i interface{} = "hello"
   // Type assertion to convert interface{} to string
   s, ok := i.(string)
   if ok {
       fmt.Println("The string is:", s)
   } else {
       fmt.Println("Type assertion failed")
   }

2. Type Switch in interface

A type switch is a more powerful way to handle multiple possible types stored in an interface. Here’s an example:

func main() {
   var i interface{}
   // Assign different types to the empty interface
   i = 42
   checkType(i)

   i = "Scalent"
   checkType(i)

   i = 8.25
   checkType(i)

   i = true
   checkType(i)
}
func checkType(i interface{}) {
   switch v := i.(type) {
   case int:
       fmt.Printf("i is an int: %d\n", v)
   case string:
       fmt.Printf("i is a string: %s\n", v)
   case float64:
       fmt.Printf("i is a float64: %f\n", v)
   case bool:
       fmt.Printf("i is a bool: %t\n", v)
   default:
       fmt.Printf("i is of an unknown type: %T\n", v)
   }
}

In the above example, i holds an integer value. The type switch switch v := i.(type) examines the dynamic type of i and executes the corresponding case block.

3. Type Assertion with Custom Types Interface

If you have custom types that implement an interface, you can use type assertions to convert between the interface and the custom type.

type ReportSender interface {
   String() string
}
type MyType struct {
   value string
}
func (m MyType) String() string {
   return m.value
}
func main() {
   var s ReportSender = MyType{"Hello, Gophers!"}
   // Type assertion to convert ReportSender to MyType
   if myType, ok := s.(MyType); ok {
       fmt.Println("The MyType value is:", myType.value)
   } else {
       fmt.Println("Type assertion failed")
   }
}

In the above example, MyType implements the ReportSender interface. The type assertion s.(MyType) converts the ReportSender interface to MyType if possible.

Summary

  • Type Assertion: Used to convert an interface to a concrete type.
  • Type Switch: Used to handle out of multiple possible types to get one stored in an interface.
  • Custom Types: You can assert interfaces to custom types that implement the interface methods.

Understanding type conversion and assertion with interfaces in Go is crucial for handling dynamic values and leveraging Go’s type system effectively.

Best Practises for Developer

The Following are the important points :

1. Type Conversion

Data Types Compatibility

Make sure that the types you are converting between are compatible. For example, you can convert an int to a float64, but not a string directly to an int without parsing.

Use Explicit Conversions

Go requires explicit type conversions to avoid implicit errors. Always use the proper conversion syntax.

var a int = 11
var b float64 = float64(a)

Be Aware of Precision Loss

Converting between types, especially numeric types, can lead to loss of precision. For example, converting a float64 to an int will truncate the decimal part.

Handle Potential Errors

When converting strings to numbers, handle potential errors.

var s string = "123"
i, err := strconv.Atoi(s)
if err != nil {
// Handle error
}

2. Type Assertion

Check for Success

Always check if the type assertion is successful to avoid panics.

var i interface{} = "hello"
s, ok := i.(string)
if ok {
       fmt.Println("String:", s)
} else {
       fmt.Println("Type assertion failed")
}

Use Single-Return Assertion Carefully

Using single-return assertion (s := i.(string)) can cause a panic if the assertion fails. Use it only when you are certain of the type. Or you can use the comma OK ( , ok) idiom.

var i interface{} = "hello"
s := i.(string) // Safe only if you are sure i is a string

3. Type Switch

Use Type Switch for Multiple Types

A type switch is useful when you need to handle multiple possible types stored in an interface.

   switch v := i.(type) {
   case int:
       fmt.Printf("i is an int: %d\n", v)
   case string:
       fmt.Printf("i is a string: %s\n", v)
   case float64:
       fmt.Printf("i is a float64: %f\n", v)
   case bool:
       fmt.Printf("i is a bool: %t\n", v)
   default:
       fmt.Printf("i is of an unknown type: %T\n", v)
   }

Default Case: Always include a default case to handle unexpected types.

Optimise for Common Cases: Order your cases from most common to least common to optimise performance.

General Tips

1. Document Your Intent

Use comments to explain why a type conversion or assertion is necessary, especially if it’s not immediately obvious.

2. Write Tests

Write tests to cover different type conversions and assertions to ensure your code handles all cases correctly.

3. Avoid Unnecessary Conversions

Minimise type conversions and assertions to keep your code clean and efficient.

Summary

Following table shows the function usage for data conversion

SN
From
To
Function
1
int
string
fmt.Sprintf()
2
int
string
strconv.Itoa()
3
int
float32
float32()
4
int
float64
float64()
5
float64
string
strconv.FormatFloat()
6
float32
string
strconv.FormatFloat()
7
string
int
strconv.Atoi()
8
string
float64
strconv.ParseFloat()
9
[]byte{}
string
string()
10
string
[]byte{}
[]byte{}()
11
int, float64,​ float32
string
fmt.Sprintf()