{ Soham Kamani }

About Blog Github Twitter

🌙
☀️

Type Assertions vs Type Conversions in Golang

Type assertions and type conversions seem to be a confusing topic in Go, since they both seem to do the same thing.

In this article, we will see how assertions and conversions are actually quite different, and go under the hood to see what happens when you use each of them in Go.

First, let’s see what they look like…

This is a type assertion in Go:

var greeting interface{} = "hello world"
greetingStr := greeting.(string)

And this is a type conversion:

greeting := []byte("hello world")
greetingStr := string(greeting)

The most obvious difference is that they have a different syntax (variable.(type) vs type(variable)). Let’s look at each case in detail.

Type Assertions

As the name suggests, type assertions are used to assert that a variable is of some type. Type assertions can only take place on interfaces.

In our type assertion example above, greeting was an interface{} type, to which we assigned a string. Now, we can say that greeting is actually a string, but the interface exposed to us is interface{}.

If we want to get the original type of greeting we can assert that it is a string, and this assertion returns its original string type.

type assertions obtain the original type from the interface

This implies that we should know the underlying type of any variable while we are doing a type assertion, which is not always the case. This is why type assertion expressions actually return a second optional value:

var greeting interface{} = "42"
greetingStr, ok := greeting.(string)

The second value, ok, is a boolean value which is true if our assertion is correct, and false otherwise.

This also implies that type assertions are performed at runtime.

Type Switch

A type switch is a useful construct that you can use when you aren’t sure of the type of an interface:

var greeting interface{} = 42

switch g := greeting.(type) {
  case string:
    fmt.Println("g is a string with length", len(g))
  case int:
    fmt.Println("g is an integer, whose value is", g)
  default:
    fmt.Println("I don't know what g is")
}

Why Is It An Assertion?

In the above examples, it may seem like you’re “converting” the type of greeting from interface{} to int or string. However, the type of greeting is fixed, and is the same as what you declare during its initialization.

When you assign greeting to an interface type, you do not change its underlying type. In the same way, when you assert its type, you’re simply using the entire original types functionality, rather than the limited methods exposed by the interface.

Type Conversions

First, let’s take a moment to understand what a “type” actually is. Each type in Go defines two things:

  1. How the variable is stored (the underlying data structures)
  2. What you can do with the variable (methods and functions it can be used in)

There are a few base types, of which string and int are included. And composite types, which include structs, maps, arrays and slices.

You can declare a new type from a base type, or by creating a composite type:

// myInt is a new type who's base type is `int`
type myInt int

// The AddOne method works on `myInt` types, but not regular `int`s
func (i myInt) AddOne() myInt { return i + 1}

func main() {
	var i myInt = 4
	fmt.Println(i.AddOne())
}

When we declared the myInt type, we based the variable data structure on the basic int type, but changed what we could do with myInt type variables (by declaring a new method on it).

Since the underlying data structure is similar for int and myInt, variables of these types can be converted between one another:

var i myInt = 4
originalInt := int(i)

Here i is of type myInt, but originalInt is of type int.

types can be converted as long as they have the same underlying data structure

When Can We Use Type Conversion?

Type’s can only be converted between one another if the underlying data structure is the same. Let’s see an example using structs:

type person struct {
	name string
	age int
}

type child struct {
	name string
	age int
}

type pet {
  name string
}

func main() {
	bob := person{
		name: "bob",
		age: 15,
		}
  babyBob := child(bob)
  // "babyBob := pet(bob)" would result in a compilation error
	fmt.Println(bob, babyBob)
}

Here, person and child have the same data structure, which is:

struct {
	name string
	age int
}

They can therefore be converted between one another.

types with different underlying data structures cannot be converted between one another

A shorthand can be used for declaring multiple types with the same data structure:

type pet person

This just means that child is based on the same data structure as person (similar to our integer example before)

Run this example here

Why Is It Called Conversion

As mentioned before, different types have different restrictions and methods defined on them, even though their data structure may be the same. When you convert from one type to another, you are changing what you can do with the type, rather than just exposing its underlying type, as is done in type assertions.

Type conversions also give you compilation errors if you try to convert to the wrong type, as opposed to runtime errors and optional ok return values that type assertions give.

Conclusion

The difference between type assertion and type conversion is more fundamental than just a difference in syntax. It also highlights the difference between interface types and non-interface (or concrete) types in Go.

An interface type does not have any underlying data structure, but rather exposes a few methods of a pre-existing concrete type (which does have an underlying data structure).

A type assertion brings out the concrete type underlying the interface, while type conversions change the way you can use a variable between two concrete types that have the same data structure.


Like what I write? Join my mailing list, and I'll let you know whenever I write another post. No spam, I promise!

Comments

Soham Kamani

Written by Soham Kamani, an author,and a full-stack developer who has extensive experience in the JavaScript ecosystem, and building large scale applications in Go. He is an open source enthusiast and an avid blogger. You should follow him on Twitter