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?
Join the discussion
comments powered by Disqus