The Comma Ok Idiom

Boston CartwrightBoston Cartwright | April 10, 2021 | golang
4 min read | ––– views
Photo by Martin Garrido on Unsplash
Photo by Martin Garrido on Unsplash

A common idiom in Go

Want to check this out later?

Introduction

I've recently been working a lot more in Go and decided to read through effective go, a large document containing tips on how to write clear, idiomatic Go code.

I love that Go is very opinionated on how things should be done, and as part of my study of this document I hope to write a number of reference articles describing some aspects of it.

Multiple return values

If you are not familiar with Go, one unique aspect of this language is that functions and methods can return multiple values.

In A Tour of Go, they use the following example to demonstrate this:

package main

import "fmt"

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

// Output: world hello

Here we see the swap function, which takes in two strings, and returns two strings.

func swap(x, y string) (string, string)

This ability to return multiple values is very powerful, and sets up many idiomatic patterns in Go. This includes error handling, comma ok, and more.

Comma ok

Let's imagine a requirement: we have a map that holds a list of employees, and we need to get a specific employee's ID number (stored as an int). We might start by creating a function with the following signature:

func SearchEmployee(name string) int

Then to use it, we can call it like so:

func NotifyEmployee(name string) {
  employeeID := SearchEmployee(name)

  // business logic with employee id
}

However this poses a problem, what if the name doesn't exist in our map? In that case, employeeID would default to the zero value, which for int is 0. In other languages, you might return a -1 to signal that the user was not found, but since Go supports multiple return values, we can follow the "comma, ok" idiom.

We would change the signature to:

func SearchEmployee(name string) (id int, ok bool)

Then call it like so:

func NotifyEmployee(name string) {
  employeeID, ok := SearchEmployee(name)
  if !ok {
    // logic to handle if employee id
    // can't be found or doesn't exist
  }

  // business logic with employee id
}

This is used in many places in the standard library and very common when working with maps.

Basic implementation of SearchEmployee

Since Go uses the "comma, ok" idiom for maps, a basic implementation of SearchEmployee could look like this:

var employees = map[string]int{
  "Alice S": 0,
  "Bob C": 1,
  "Doug F": 2,
  // ... other employees
}

func SearchEmployee(name string) (id int, ok bool) {
  if employee, ok := employees[name]; ok {
    return employee, true
  }
  return 0, false
}

Other uses

This same pattern is followed for type assertions, another topic I'd love to write about it's power in the future! Here is a sneak peek:

truck, ok := vehicle.(Truck)
if ok {
  fmt.Println("🚛")
} else {
  fmt.Println("not a 🚛")
}

There are many places where the "comma, ok" pattern is useful in Go and I've found it to be a clean idiom that is very nice to use.

What do you think? Let me know @bstncartwright