When programming in Go, using types is one of the most fundamental language features that we use.

Occasionally, we will need to convert between types and interfaces. For example, we may need to convert an int type to a long type. Or maybe we have to convert a string to a []byte type.

In this article, we will see how to convert between types in Go, and when to use type assertions and type conversions.

banner

If you just want to see the code, you can view all the examples on Github

Type Conversion

The syntax to convert from type t1 to type t2 is:

variableOfTypeT2 := t2(variableOfTypeT1)

Let’s look at a simple example to convert a []byte to a string type:

// right now, `greeting` is of type `[]byte`
greeting := []byte("hello world")
// over here, we are converting `greeting` to a `string` type
// and assigning it to `greetingStr`
greetingStr := string(greeting)

Compatible Types For Conversion

You can only convert between types that are compatible with one another. For example, you can convert between int and int64, but not between int and string.

Before we get into the details of how this works, 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 structure)
  2. What you can do with the variable (methods and functions it can be used in)

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 struct {
	name string
}

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

Try it here

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

struct {
	name string
	age int
}

They can therefore be converted between one another. But, we cannot convert them to (or from) a pet type.

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 child person

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

Type Conversion Between Basic Types

Unlike the examples we saw before, basic types like int and string do not have an explicitly defined data structure. This is because they are built into the language, and are not defined by the user.

Because of this, there are some special rules that apply when converting between basic types:

  1. int can be converted to float64:

    var n int = 5
    fmt.Println(float64(n))
    // output:
    // 5
    
  2. float64 can be converted back to int, but the fraction is eliminated:

    var pi float64 = 3.1412
    fmt.Println(int(pi))
    // output:
    // 3
    
  3. Strings can be converted to byte arrays:

    greeting := "hello world"
    fmt.Println([]byte(greeting))
    // output: 
    // [104 101 108 108 111 32 119 111 114 108 100]
    

Try it here.

We can see the full list of special cases in the Go language specification, but these are the most common ones.

Type Conversion Between Interfaces

Interfaces work a bit differently than concrete types. An interface does not have an underlying data structure, but rather exposes a few methods of a pre-existing concrete type.

So, as long as the underlying concrete type has implemented the methods of the interface, you can convert between interfaces.

First, let’s look at an example where we have two interfaces that define the same method for implementation:

// here, phone and smartPhone are interfaces with the same method
// so any type that implements phone can be converted to a smartPhone
// and vice versa
type phone interface {
	call()
}

type smartPhone interface {
	call()
}

// myPhone implements phone, as well as smartPhone
type myPhone struct {
}

func (p myPhone) call() {
	fmt.Println("ring ring")
}

In these cases, there is actually no conversion required, since interface implementations are implicit in Go. So, we can simply assign a myPhone type to a phone or smartPhone type:

var p phone = myPhone{}
var sp smartPhone = p
sp.call()

Try it here.

For converting between interfaces that do not have the same methods, we can use a type assertion, which we will see in the next section.

Type Assertion

We use type assertions to assert that a variable is of some type. Type assertions can only take place on interfaces.

Let’s see an example:

var greeting interface{} = "hello world"
// the syntax of type assertion is `variable.(type)`
greetingStr := greeting.(string)
  1. In the first line, we declare a variable greeting of type interface{}. This means that greeting can be assigned any type, and we can only access the methods of the interface{} type.
  2. In the second line, we assert that greeting is actually a string type, and assign it to greetingStr.

type assertions obtain the original type from the interface

Type Assertion to Convert Between Interfaces

Type assertions can be used to convert between interfaces, based on the underlying variable.

Let’s go back to our smart phone example from before. This time, we’ll add another method to the smartPhone interface:

type phone interface {
  call()
}

type smartPhone interface {
  call()
  // this is a new method that is not in the phone interface
  browse()
}

// myPhone implements phone, as well as smartPhone
// since it has both the call() and browse() methods
type myPhone struct {
}

func (p myPhone) call() {
  fmt.Println("ring ring")
}

func (p myPhone) browse() {
  fmt.Println("browsing the internet")
}

In this case, we cannot just assign a phone type to a smartPhone type like before, since the phone interface does not have the browse() method.

Instead, we can use a type assertion to achieve this:

var p phone = myPhone{}
// this is a type assertion, which converts the `phone` type to a `smartPhone` type
var sp smartPhone = p.(smartPhone)
// now we can use methods of the `smartPhone` interface
sp.browse()

Note, that this is only possible because myPhone implements both the phone and smartPhone interfaces. If it only implemented the phone interface, we would get a runtime error:

// myOtherPhone only implements the phone interface, and not the smartPhone interface
type myOtherPhone struct {
}

func (p myOtherPhone) call() {
  fmt.Println("ring ring")
}

func main(){
  var p phone = myOtherPhone{}
  // this will result in a runtime error
  var sp smartPhone = p.(smartPhone)
	sp.browse()
  // output:
  // panic: interface conversion: main.myOtherPhone is not main.smartPhone: missing method browse
}

Try it here.

💡 To assert that a variable is of an interface type, the underlying variable must implement that interface. Otherwise, you will get a runtime error.

Type Conversion vs Type Assertion

Type conversion and type assertion are two different things, and are used in different situations.

Type conversion is used to convert between two types that have the same underlying data structure.

Whereas, type assertion is used to convert between interfaces, or to convert an interface to a concrete type.

During type assertion, the underlying concrete type of the variable does not change.

Additionally, type assertions are performed at runtime, so you will only get an error if the assertion is incorrect. Type conversions, on the other hand, are performed at compile time, so you will get a compilation error if the conversion is incorrect.

You can see all the working code from the examples in this post on Github