Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explain Type Assertions in Go

Tags:

go

People also ask

What is type assertion in Go?

A type assertion provides access to an interface value's underlying concrete value. t := i.(T) This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t . If i does not hold a T , the statement will trigger a panic.

What is a type in Go?

Type is the base interface for all data types in Go. This means that all other data types (such as int , float , or string ) implement the Type interface. Type is defined in the reflect header.

What is type switch in Golang?

A switch is a multi-way branch statement used in place of multiple if-else statements but can also be used to find out the dynamic type of an interface variable.

Is type assertion bad?

Type assertion in TypeScript is the as syntax and angle-bracket syntax made available by TypeScript to 'assert' any TypeScript identifier to a type of the implementer's choosing. However, this can easily be misused and lead to anti-patterns, brittle code requiring shotgun surgery.


Short answer

In one line:

x.(T) asserts that x is not nil and that the value stored in x is of type T.

Why would I use them:

  • to check x is nil
  • to check if it's convertible (assert) to another type
  • convert (assert) to another type

What exactly they return:

  • t := x.(T) => t is of type T; if x is nil, it panics.

  • t,ok := x.(T) => if x is nil or not of type T => ok is false otherwise ok is true and t is of type T.


Detailed explanation

Imagine you need to calculate area of 4 different shapes: Circle, Square, Rectangle and Triangle. You may define new types with a new method called Area(), like this:

type Circle struct {
    Radius float64
}
func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

And for Triangle:

type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}

And for Rectangle:

type Rectangle struct {
    A, B float64
}

func (t Rectangle) Area() float64 {
    return t.A * t.B
}

And for Square:

type Square struct {
    A float64
}
func (t Square) Area() float64 {
    return t.A * t.A
}

Here you have Circle, with radius of 1.0, and other shapes with their sides:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

Interesting! How can we collect them all in one place?
First you need Shape interface to collect them all in one slice of shape []Shape :

type Shape interface {
    Area() float64
}

Now you can collect them like this:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

After all, Circle is a Shape and Triangle is a Shape too.
Now you can print the area of each shape using the single statement v.Area():

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
}

So Area() is a common interface between all shapes. Now, how can we calculate and call uncommon method like angles of triangle using above shapes?

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

Now it's time to extract Triangle from above shapes:

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
    if t, ok := v.(Triangle); ok {
        fmt.Println("Angles:", t.Angles())
    }
}

Using t, ok := v.(Triangle) we requested type assertions, meaning we asked the compiler to try to convert v of type Shape to type Triangle, so that if it's successful, the ok will be true otherwise false, and then if it is successful call t.Angles() to calculate the triangle's three angles.

This is the output:

Circle (Radius: 1)  Area: 3.141592653589793
Square (Sides: 1.772453)    Area: 3.1415896372090004
Rectangle (Sides: 5, 10)    Area: 50
Triangle (Sides: 10, 4, 7)  Area: 10.928746497197197
Angles: [128.68218745348943 18.194872338766785 33.12294020774379]

And the whole working sample code:

package main

import "fmt"
import "math"

func main() {
    shapes := []Shape{
        Circle{1.0},
        Square{1.772453},
        Rectangle{5, 10},
        Triangle{10, 4, 7},
    }
    for _, v := range shapes {
        fmt.Println(v, "\tArea:", v.Area())
        if t, ok := v.(Triangle); ok {
            fmt.Println("Angles:", t.Angles())
        }
    }
}

type Shape interface {
    Area() float64
}
type Circle struct {
    Radius float64
}
type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
type Rectangle struct {
    A, B float64
}
type Square struct {
    A float64
}

func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

// Heron's Formula for the area of a triangle
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
func (t Rectangle) Area() float64 {
    return t.A * t.B
}

func (t Square) Area() float64 {
    return t.A * t.A
}

func (t Circle) String() string {
    return fmt.Sprint("Circle (Radius: ", t.Radius, ")")
}
func (t Triangle) String() string {
    return fmt.Sprint("Triangle (Sides: ", t.A, ", ", t.B, ", ", t.C, ")")
}
func (t Rectangle) String() string {
    return fmt.Sprint("Rectangle (Sides: ", t.A, ", ", t.B, ")")
}
func (t Square) String() string {
    return fmt.Sprint("Square (Sides: ", t.A, ")")
}

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

Also see:

Type assertions

For an expression x of interface type and a type T, the primary expression

x.(T)  

asserts that x is not nil and that the value stored in x is of type T. The notation x.(T) is called a type assertion.

More precisely, if T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T. In this case, T must implement the (interface) type of x; otherwise the type assertion is invalid since it is not possible for x to store a value of type T. If T is an interface type, x.(T) asserts that the dynamic type of x implements the interface T.

If the type assertion holds, the value of the expression is the value stored in x and its type is T. If the type assertion is false, a run-time panic occurs. In other words, even though the dynamic type of x is known only at run time, the type of x.(T) is known to be T in a correct program.

var x interface{} = 7  // x has dynamic type int and value 7
i := x.(int)           // i has type int and value 7

type I interface { m() }
var y I
s := y.(string)        // illegal: string does not implement I (missing method m)
r := y.(io.Reader)     // r has type io.Reader and y must implement both I and io.Reader

A type assertion used in an assignment or initialization of the special form

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)

yields an additional untyped boolean value. The value of ok is true if the assertion holds. Otherwise it is false and the value of v is the zero value for type T. No run-time panic occurs in this case.


EDIT

Question: What does the assertion x.(T) return when T is an interface{} and not a concrete type?
Answer:

It asserts that x is not nil and that the value stored in x is of type T.

E.g. this panics (compile: Success, Run: panic: interface conversion: interface is nil, not interface {}):

package main

func main() {
    var i interface{} // nil
    var _ = i.(interface{})
}

And this works (Run: OK):

package main

import "fmt"

func main() {
    var i interface{} // nil
    b, ok := i.(interface{})
    fmt.Println(b, ok) // <nil> false

    i = 2
    c, ok := i.(interface{})
    fmt.Println(c, ok) // 2 true

    //var j int = c // cannot use c (type interface {}) as type int in assignment: need type assertion
    //fmt.Println(j)
}

Output:

<nil> false
2 true

NOTE: here c is of type interface {} and not int.


See this working sample code with commented outputs:

package main

import "fmt"

func main() {
    const fm = "'%T'\t'%#[1]v'\t'%[1]v'\t%v\n"
    var i interface{}
    b, ok := i.(interface{})
    fmt.Printf(fm, b, ok) // '<nil>'    '<nil>' '<nil>' false

    i = 2
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'int'  '2' '2' true

    i = "Hi"
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'string'   '"Hi"'  'Hi'    true

    i = new(interface{})
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // '*interface {}'    '(*interface {})(0xc042004330)' '0xc042004330'  true

    i = struct{}{}
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'struct {}'    'struct {}{}'   '{}'    true

    i = fmt.Println
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(...interface {}) (int, error)'   '(func(...interface {}) (int, error))(0x456740)'    '0x456740'  true

    i = Shape.Area
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(main.Shape) float64' '(func(main.Shape) float64)(0x401910)'  '0x401910'  true
}

type Shape interface {
    Area() float64
}

Common usecase: check if returned error is of a type T.

https://golang.org/ref/spec#Type_assertions

For a single return value assertion: when it fails the program panics.

For a two return values assertion: when it fails second argument is set to false and the program doesn't panic.


A type assertion is the x.(T) notation where x is of interface type and T is a type. Additionally, the actual value stored in x is of type T, and T must satisfy the interface type of x.