Go tip of the week: It's time to switch!

Surely you know the “standard” way of branching into one of multiple code paths depending on a given value:

switch a {
	case 1: 
		fmt.Println("1")
	case 2:
		fmt.Println("2")
	default:
		fmt.Println("default")
}

That's how switch works in Go and in many other languages (except that in Go, no fallthrough to subsequent cases happens).

But the switch statement can do more. Here are a few variants.

1. Multiple values in a case

A case can list multiple values to match:

switch a {
	case 1: 
		fmt.Println("1")
	case 2, 3, 4:
		fmt.Println("2, 3, or 4")
//	case 1,2:  // error: duplicate case 1, duplicate case 2
//		fmt.Println("1 or 2")
}

A given value can only be used for one case block. Duplicate case values trigger an error.

2. An initializer like in a for loop

You may initialize a value before switching on it. The scope of a is limited to the switch construct:

switch a := f(); a {
	case 1: 
		fmt.Println("1")
	case 2:
		fmt.Println("2")
}

3. No switch expression but case expressions

Cases are not restricted to static values. If you omit the switch expression, you can use expressions for each case:

switch {
	case a == 1: 
		fmt.Println("1")
	case a >=2 && a <= 4:
		fmt.Println("2")
	case a <= 5:
		fmt.Println("3")
}

If the current value of a matches more than one case, the first matching case is chosen.

4. Switching on a variable's type

If your code receives an interface value from somewhere, a type switch can check the actual type of this value:

switch v := a.(type) {
	case int: 
		fmt.Println("a is an int:", v)
	case string, []byte:
		fmt.Println("a is a string:", v)
}

5. Switching on a type parameter

This one may seem a bit esoteric: You can define a generic function where the type parameter is not used for the parameter list. Instead, a switch statement can refer to this parameter to check a parameter's value:

func do[T comparable](a any) {
	switch v := a.(type) {
	case int:
		fmt.Println("a is an int:", v)
	case T:
		fmt.Printf("a is a %T: %v", v, v)
	case []T:
		fmt.Println("a is a slice:", v)
	case []byte:
		fmt.Println("a is a byte slice:", v)
	}
}

func main() {
	do[bool](a)
	do[bool](true)
	do[int]([]int{1, 2, 3})
}

As with case expressions, if the actual type of a matches multiple cases, the first matching case is chosen.

(Play with all these options in the Go Playground.)

Switch your way of switching

With that many options, the switch statement leaves little to desire. For more details, check the Switch statements section of the Go language reference.