Momchil Atanasov
mokiat's blog

Follow

mokiat's blog

Follow

Beware of Go's interfaces

Momchil Atanasov's photo
Momchil Atanasov
·Nov 3, 2022·

5 min read

Concurrency, channels, cross-compilation are all concepts that come to mind when talking about Go — all aspects that make the language brilliant. There is one concept, however, that isn’t that much new but is quite important in Go as well — interfaces.

The fact that interfaces are implemented implicitly makes for a very flexible language, where modular pieces of code are a breeze to write.

That said, Go’s interfaces do have a kind of dark side to them — one that can easily catch newcomers by surprise and throw them off balance. Let me demonstrate this with an example.

Let’s say we have an interface called StringProvider that looks as follows.

type StringProvider interface {
    Provide() string
}

And let’s write a very simple struct that implements that interface.

type hardcodedStringProvider struct {
    Value string
}

func (p *hardcodedStringProvider) Provide() string {
    return p.Value
}

Since the struct is private, we need to create some type of public constructor function for it.

func NewStringProvider(content string) StringProvider {
    var provider *hardcodedStringProvider = nil
    if content != "" {
        provider = &hardcodedStringProvider{
            Value: content,
        }
    }
    return provider
}

If a non-empty string argument is passed, we return an instance of *hardcodedStringProvider, abstracted through the StringProvider interface. Otherwise we return nil. Or, at least, that’s the main idea.

And before you start to dwell on why anyone would ever implement a constructor function this way, keep in mind that it is for the sake of the example.

Finally, we have a section of code that makes use of our interface and implementation.

func main() {
    provider := NewStringProvider(os.Args[1])
    if provider != nil {
        fmt.Printf("Content: %s\n", provider.Provide())
    } else {
        fmt.Println("No string provider!")
    }
}

Passing an argument like Hello to the program should print Content: Hello, and this is exactly the case. Similarly, we would expect that passing an empty argument (this can be done with go run main.go "") should produce No string provider!. Unfortunately, this is not what happens.

Instead, you get a nil pointer dereference and your application crashes.

And the reason for this is because the NewStringProvider function returns a value that is somewhat nil but returns false when compared against nil.

If you are new to Go, you might be banging your head against the wall right now, looking at the code and clearly seeing that we defined the provider variable to be nil by default. What’s going on?

You are actually right. The variable provider is nil by default. The problem arises when you return the variable from the function. Since the return type of the function is of type StringProvider, which is an interface, the variable gets converted to such an interface type.

Unlike other programming languages, where the interface acts and appears to be a simple pointer, in Go it’s actually a struct value.

Here is a very oversimplified representation of what an interface is.

type interface struct {
    type *Type
    value *Value
}

It has two fields — one points to the type of the thing that acts as this interface, and the other points to the actual instance of that type.

The thing is, an interface is nil only when both of its fields are nil. If you have a type set, even if the value is nil, then the interface is no longer nil. And this is exactly the case with the code above. When we return provider and have it converted to an interface, the value is nil, but we have a type, which is *hardcodedStringProvider, hence the returned interface is not really nil. Yet, trying to access any of the methods of that interface does produce a nil pointer dereference error, as we don’t have an instance on which to call the method.

There are two ways to fix the code above.

The first one is to specify the type of the variable provider as an interface from the start, so that by setting its value to nil, it will actually be nil, as there won't be any type at that moment in time.

func NewStringProvider(content string) StringProvider {
    var provider StringProvider = nil
    if content != "" {
        provider = &hardcodedStringProvider{
            Value: content,
        }
    }
    return provider
}

The second way, and the one I prefer most, is to explicitly return the keyword nil whenever you want to return nil. It is failsafe and much easier to read.

func NewStringProvider(content string) StringProvider {
    if content == "" {
        return nil
    }
    return &hardcodedStringProvider{
        Value: content,
    }
}

I know that most people would have written the code as in the last example from the get go, but I have seen snippets of code where this problem was very likely to occur but would have been much more obscured.

Note: This is especially dangerous when dealing with the error type in Go, as it is actually an interface and is a very common return type for a function.

Before I finish, I want to say that this is not the first article on the matter. I have come across one other publication on the net, which is much more precise on the technical part of the inner workings of interfaces in Go. But since I have seen how people that have used the language for a while are not aware of this, I decided that one more warning out there cannot be bad.

I hope this article was useful to you and helps avoid long and tiresome troubleshooting sessions in the evenings.

You can test the code from this article here: Go Playground


Note: This article has been ported from medium.

 
Share this