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.
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:
- How the variable is stored (the underlying data structure)
- 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)
}
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.
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:
int
can be converted tofloat64
:var n int = 5 fmt.Println(float64(n)) // output: // 5
float64
can be converted back toint
, but the fraction is eliminated:var pi float64 = 3.1412 fmt.Println(int(pi)) // output: // 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]
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()
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)
- In the first line, we declare a variable
greeting
of typeinterface{}
. This means thatgreeting
can be assigned any type, and we can only access the methods of theinterface{}
type. - In the second line, we assert that
greeting
is actually astring
type, and assign it togreetingStr
.
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
}
💡 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