Converting Types in Go

While programming, we often need type conversions. If you come from a language that usually does implicit conversions, converting types in Go can be a bit of a hassle.

First of all, let’s start with some basics. Go is a statically and strongly typed programming language. This means that, variable types must be known at compile time. Additionally, type conversions must be made explicitly.

Type Conversions

In Go, a simple type conversion can be performed in a form like:

t = TypeToConvert(Value)

As you can see in the examples below, all conversions are done explicitly and exactly to the type we want. We can easily convert numeric types as long as they represent each other. However, keep in mind that precision loss may occur in these transformations.

a := 5;
fmt.Printf("type: %T, value: %v \n", a,a)

b := float32(a);
fmt.Printf("type: %T, value: %v \n", b,b)

c := 5.5
fmt.Printf("type: %T, value: %v \n", c,c)

d := int(c)
fmt.Printf("type: %T, value: %v \n", d,d)

type: int, value: 5 
type: float32, value: 5 
type: float64, value: 5.5 
type: int, value: 5 

How about conversions between int and string?

In Go, int and string types do not directly represent each other. For this reason, we cannot apply the method we have just done.

x:= int("5")

cannot convert "5" (untyped string constant) to type int

Fortunately, strconv package provides built-in methods for such conversions.

x := "5"
fmt.Printf("type: %T, value: %v \n", x,x)

y, _ := strconv.Atoi(x) // (ascii string to int)
fmt.Printf("type: %T, value: %v \n", y,y)

z := strconv.Itoa(y) // (int to ascii string)
fmt.Printf("type: %T, value: %v \n", z,z)

type: string, value: 5 
type: int, value: 5 
type: string, value: 5  

Go is fairly strict about types, for example you can’t mix numeric types in an expression. You can’t even mix 32-bit type int and int32. This strictness prevents many bugs from occurring, but requires you to explicitly make the necessary conversions at all times.

Untyped constants?

When it comes to constants, Go introduced a concept called untyped constants. This means that if you create a constant that does not have an explicit type, it will not have a fixed type unless a defined type is given.

const a = "Hi" //no explicit type is defined
fmt.Printf("type: %T, value: %v \n", a,a)

var b string = a // b has a defined type string
fmt.Printf("type: %T, value: %v \n", b,b)

type NewString string
var c NewString = a // c has a defined type NewString
fmt.Printf("type: %T, value: %v \n", c,c)

type: string, value: Hi 
type: string, value: Hi 
type: main.NewString, value: Hi 

In the example above, although const a has a default type of string(determined by its syntax), it does not have a fixed type. This allows you to assign it to another variable with a defined type. Of course, if it had a predefined type, you couldn’t assign it to any other type without doing the necessary conversion.

type NewString string
const a NewString = "Hi"
fmt.Printf("type: %T, value: %v \n", a,a)
//var b string = a // does not compile
var b string = string(a) // conversion is needed
fmt.Printf("type: %T, value: %v \n", b,b)

type: main.NewString, value: Hi 
type: string, value: Hi 

Once a constant has a defined type, the rule of not mixing different types continues to apply.

In the example below, const a is untyped, but is inferred as NewString when creating c . On the other hand, d has a defined typed of string that cannot be mixed with other types such as NewString.

const a = "Ha"
fmt.Printf("type: %T, value: %v \n", a,a)

type NewString string
const b NewString = "Ha"

var c  = b + a // a is untyped and inferred as NewString
fmt.Printf("type: %T, value: %v \n", c,c)

const d string = "Ha" // d has a defined type string
// var e  = b + d // does not compile, type Mismatch

type: string, value: Ha 
type: main.NewString, value: HaHa 

Type Assertions

When converting interface value’s to the underlying concrete types, Go provides us with a mechanism called type assertions. The following form is used for such assertions.

t,conversionResult := interface.(TypeToConvert)

If you have used java before, you can think of it like the “instanceOf” method.

In the following example, since interface i has a concrete type of A, we can convert it to A by assertion.

type A struct{
	Name string

var i interface{} = A{Name:"myName"}  
fmt.Printf("type: %T, value: %v \n", i,i)

if a,ok := i.(A); ok{ // can be converted to A
	fmt.Printf("type: %T, value: %v \n", a,a.Name)

if aStr,ok := i.(string); ok{ //cannot convert to string
	fmt.Printf("type: %T, value: %v \n", aStr,aStr)

type: main.A, value: {myName} 
type: main.A, value: myName 

As a result, in Go, we use type conversion for basic types and type assertion for interfaces. Additionally, some built-in methods allow us to convert types those do not directly represent each other. In conversions of constants, it may be necessary to pay attention to whether the type is explicitly defined or not.