Understanding Go's Empty Interface

Understanding Go's Empty Interface

Recently, while working with the Twilio Lookup API, I had the opportunity to properly learn about Go’s empty interface. During the process, I realised that I didn’t understand it properly, so took the opportunity to do so. Today, I am going to step through what it is and how simple they are to work with.


So, what is the empty interface?

Here’s a short quote from the Go Tour:

An empty interface may hold values of any type. Empty interfaces are used by code that handles values of unknown type.

They’re one of the excellent ways that Go, while being a statically-typed language, also has some of the benefits of dynamically-typed languages, such as PHP, Ruby, and Python. In the context of an API, for example, the empty interface affords the flexibility of returning data only when it’s available. You don’t have to set an, effectively, empty value, just for the sake of doing so.

While I’m broadly familiar with them, I’d never really worked with them that much before. However I had to, recently, while working with Twilio’s Lookup API using Twilio’s Go Helper Library.

If you’re not familiar with the API:

The Lookup v2 API allows you to query information on a phone number so that you can make a trusted interaction with your user. With this endpoint, you can format and validate phone numbers with the free Basic Lookup request and add on data packages to get even more in-depth carrier and caller information.

Have a look at the short example below.

package main

import (
	"fmt"
	"github.com/twilio/twilio-go"
	lookups "github.com/twilio/twilio-go/rest/lookups/v1"
)

func main() {
	client := twilio.NewRestClient()

	params := &lookups.FetchPhoneNumberParams{}
	params.SetType([]string{"caller-name"})

	resp, err := client.LookupsV1.FetchPhoneNumber("+15108675310", params)
}

The call to FetchPhoneNumber() does a check on +15108675310 and, if available, returns caller name details in the returned LookupsV2PhoneNumber object.

If you look at LookupsV2PhoneNumber’s definition, you’ll see that the CallerName property stores a phone number’s caller name details, and is defined as a pointer to an empty interface, (*interface{}).

Defining it this way affords it the ability to store caller name details — if they’re available. It doesn’t mandate that the information must always be available.

Unfortunately (at least for this little learner) it left me wondering how to access the information, if it was available.

To the more seasoned Go developers, it likely wouldn’t even be a thought. But, if you’re like me and still learning Go, then (at first at least) you might not know what to do.

I kept thinking about what I might do if this were PHP

For example, when working with laminas-hydrator, I’d hydrate an object with the data in the response, as in the following example (borrowed from the documentation):

<?php

$hydrator = new Laminas\Hydrator\ArraySerializableHydrator();

$data = [
    'first_name'    => 'James',
    'last_name'     => 'Kahn',
    'email_address' => 'james.kahn@example.org',
    'phone_number'  => '+61 419 1234 5678',
];

$object = $hydrator->hydrate($data, new ArrayObject());

Here, it’s hydrating a new ArrayObject ($object) from the contents of $data.

After that, I’d be able to check if an element was set by calling ArrayObject’s offsetExists() method. If it did exist, I’d retrieve its value by calling offsetGet(), as in the following example.

if ($object->offsetExists('first_name')) {
    printf("%s\n",$object->offsetGet('first_name'));
}

Perhaps that was my problem. I was still used to thinking in PHP, and dynamically-typed languages more generally.

After a bit of googling and learning more about the empty interface, it became clear as to what was required. I also had a bit of a facepalm moment when I realised just how simple it was too. 🤦🏼

Specifically, here’s what I did.

if resp.CallerName != nil {
  caller := *resp.CallerName
  callerDetails, _ := caller.(map[string]interface{})
  if callerName, ok := callerDetails["caller_name"]; ok && callerName != "" {
    message = fmt.Sprintf("%s It is registered to: %s", message, callerName)
  }
}

If the CallerName was not nil, I dereferenced it into a new variable named caller. Then, I cast the value to a new object named callerDetails.

For what it’s worth, if CallerName is set, it will be a map with string keys. None of the keys need to be set, because data may not be available for them either, hence why they were cast as an empty interface.

Anyway, I digress. At this point, I checked if a key was set and had a value. If so, then the value was printed.

That’s a broad introduction to Go’s empty interface

If, like me, you’re coming to Go from another language — especially a dynamically-typed one — Go’s empty interface might seem a little strange. Actually, they might even seem off-putting too.

They don’t need to be! If it helps, just think of them as allowing a value to be empty or to be set. Then, check if the value is set, If so, cast it appropriately, then work with the object as you otherwise would.

What do you think? Did I over-complicate the empty interface, or is this a common experience when first encountering them? Actually, what do you commonly do?


You might also be interested in...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.


Join the discussion

comments powered by Disqus