About type switch usage in Go

One of the features that I have been relying a lot in Go is the type switch. This feature is specially useful when using interfaces since it helps us avoid having to do type assertions all the time.

A couple of examples below.

Example #1

Let’s suppose that we have 3 types A, B & C, where each one of these types can be assigned a Name:

package main

import (
  "fmt"
  "reflect"
)

type A struct { Name string }
type B struct { Name string }
type C struct { Name string }

And we put them inside of a slice which can contain interfaces:

func main() {

  a := &A{Name: "A"}
  b := &B{Name: "B"}
  c := &C{Name: "C"}

  t := make([]interface{}, 0)
  t = append(t, a)
  t = append(t, b)
  t = append(t, c)

Without using type switch

We would end up doing something like this:

fmt.Println("--- without type switching")
for _, thing := range t {
  switch thing.(type) {
    case *A:
      fmt.Println(reflect.TypeOf(thing), "is of type A. Name is:", thing.(*A).Name)
    case *B:
      fmt.Println(reflect.TypeOf(thing), "is of type B. Name is:", thing.(*B).Name)
    case *C:
      fmt.Println(reflect.TypeOf(thing), "is of type C. Name is:", thing.(*C).Name)
  }
}
Results
--- without type switching
*main.A is of type A. Name is: A
*main.B is of type B. Name is: B
*main.C is of type C. Name is: C

Using a type switch

Makes things a little more bearable:

fmt.Println("--- type switching on the item")
for _, thing := range t {
  switch o := thing.(type) {
    case *A:
      fmt.Println(reflect.TypeOf(o), "is of type A. Name is:", o.Name)
    case *B:
      fmt.Println(reflect.TypeOf(o), "is of type B. Name is:", o.Name)
    case *C:
      fmt.Println(reflect.TypeOf(o), "is of type C. Name is:", o.Name)
  }
}
Results
--- type switching on the item
*main.A is of type A. Name is: A
*main.B is of type B. Name is: B
*main.C is of type C. Name is: C

Example #2

Let’s see how far we can take it. Now, let’s suppose that we want to handle A and B the same way.

Using type switch, it works too

Since we want to handle A and B the same way, we could think that we could group them into the same case, which would work:

fmt.Println("--- Grouping A and B: ")
for _, thing := range t {
  switch o := thing.(type) {
    case *A, *B:
      fmt.Println(reflect.TypeOf(o), "is of type A or B.")
    case *C:
      fmt.Println(reflect.TypeOf(o), "is of type C.")
  }
}
Results
*main.A is of type A or B.
*main.B is of type A or B.
*main.C is of type C.

Until it doesn’t

Let’s suppose that we want to inspect the value of the Name field. Then it breaks:

for _, thing := range t {
	switch o := thing.(type) {
	case *A, *B:
		fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", o.Name)
	case *C:
		fmt.Println(reflect.TypeOf(o), "is of type C. Name is:", o.Name)
	}
}

The above would throw the following error:

o.Name undefined (type interface {} has no field or method Name)

Back to interface

What happened here is that by trying to group A, B types, we ended up again with an interface, so we cannot rely on the first type switch anymore.

We could type switch once more time then:

fmt.Println("--- Double type switch all the way")
for _, thing := range t {
	switch o := thing.(type) {
	case *A, *B:
		switch oo := o.(type) {
		case *A:
			fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", oo.Name)
		case *B:
			fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", oo.Name)
		}
	case *C:
		fmt.Println(reflect.TypeOf(o), "is of type C. Name is:", o.Name)
	}
}
Results
--- Double type switch all the way
*main.A is of type A or B. Name is: A
*main.B is of type A or B. Name is: B
*main.C is of type C. Name is: C

…which looks a bit messy. A more straightforward way would be to flinch away our desire to make things “DRY”, still rely on the first type switch and just repeat more code:

fmt.Println("--- The Go Way™")
for _, thing := range t {
	switch o := thing.(type) {
	case *A:
		fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", o.Name)
	case *B:
		fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", o.Name)
	case *C:
		fmt.Println(reflect.TypeOf(o), "is of type C. Name is:", o.Name)
	}
}

…which results in:

--- The Go Way™
*main.A is of type A or B. Name is: A
*main.B is of type A or B. Name is: B
*main.C is of type C. Name is: C

Progress so far

So which one of the approaches is better?

I would say that probably the one with the multiple case statements where the same line is repeated, since when we type switch and have a case statement with one more type, we end up once again with an interface, and we need yet another type switch for it that generates more code which more or less says the same thing, so it seems that it is about as DRY as it could get for now using only type switch.

Link to the Go playground with the example

To solve the grouping problem, there is something else that we can try besides only using the type switch, which is using interface types.

Example #3

Another alternative would be to use a Nameable interface when creating the slice. This means first having to declare something like this:

type Nameable interface {
  GetName() string
}

type BasicAttrs struct { 
  Name string
}

// Code generation could help with this...
type A struct { BasicAttrs }
func (o *A) GetName() string { return o.Name }

type B struct { BasicAttrs }
func (o *B) GetName() string { return o.Name }

type C struct { BasicAttrs }
func (o *C) GetName() string { return o.Name }

So that we can later on use it as follows:

func main() {

	a := &A{BasicAttrs:BasicAttrs { Name: "A" }}
	b := &B{BasicAttrs:BasicAttrs { Name: "B" }}
	c := &C{BasicAttrs:BasicAttrs { Name: "C" }}

	t := make([]Nameable, 0)
	t = append(t, a)
	t = append(t, b)
	t = append(t, c)

	fmt.Println("--- The correct Go way?")
	for _, thing := range t {
	switch o := thing.(type) {
	   case *A, *B:
	      fmt.Println(reflect.TypeOf(o), "is of type A or B. Name is:", o.GetName())
	   case *C:
	     fmt.Println(reflect.TypeOf(o), "is of type C. Name is:", o.GetName())
	  }
	}
}
Results
--- The correct Go way?
*main.A is of type A or B. Name is: A
*main.B is of type A or B. Name is: B
*main.C is of type C. Name is: C

Is this better?

I think so. Even though the implementation is a bit more verbose, the last example using a type interface might be the way to go in case we face the grouping issue from the raw interface.