Wednesday, January 4, 2017

Fun with Interfaces and Pointers in Go

Now that I am doing server-side programming again, I have fallen in love with the Go language. Long story short, it provides just about everything I need to produce quality server-side code and deliberately withholds language features that have unintended consequences in code quality and maintenance. I believe this has enabled me to produce higher quality code faster than with other languages like Java.

However, it has not always been smooth sailing. I recently hit an unexpected bump in what I thought was fairly simple code. As it turned out, I had not fully grasped how Go handles interfaces and pointers. Allow me to explain the scenario, and in so doing, illustrate how Go interfaces and pointers work. (I only present the minimal code needed to get the point across. Anything extraneous to the point is omitted.)
  1. I wanted to have a custom error struct that had some state information in it. This was done by creating a struct called Error that implements the error interface's one required function, Error().
    type Error struct {
    Message string
    }

    func (e Error) Error() string {
    return e.Message
    }
  2. I wanted Error to lazily initialize its state. For this to work properly (i.e., retain that state after multiple calls to Error()), I needed to use a pointer receiver in my Error() function. Note the difference between the code below and the code above.
    type Error struct {
    Message string
    }

    func (e *Error) Error() string {
    if e.Message == "" {
    e.Message = time.Now().String()
    }
    return e.Message
    }
  3. For functions like foo() that are only ever going to return my own struct, I figured that I could return *Error so that I would have full access to its members without any type checking. (As you will see later, this turned out to be a bad idea!)
    func foo() *Error {
    return nil
    }
  4. Then I could subsequently return the results (an error or nil) to a higher function.
    func bar() error {
    return foo()
    }
  5. Finally I would be able to call my bar function from high level code and check the return value.
    e := bar()
    if e == nil {
    fmt.Println("Hello, playground")
    } else {
    fmt.Println("Message:" + e.Error())
    }

There is just one problem – this code segfaults. Try it out here.

Why this happens is probably not obvious to most inexperienced Go programmers. What is it that implements the error interface? Not Error, but rather *Error. The problem is that bar() must return a) something that implements the error interface or b) nil. Well, *Error implements that interface. As written here (without all of the other logic that makes the function meaningful) foo() returns a pointer to nil. A pointer to nil is not the same as nil. Repeat that to yourself, more than once if you have to.

So what have I done? I created a struct, used a pointer receiver to implement an interface, and called a function that returns a pointer to my struct, and returned that pointer (that just so happens to point to nil). Well crap, since nil is not the same as a pointer to nil the equality expression fails and we try to dereference a nil pointer. Kaboom! The fix is simple – change foo() to return error instead of *Error. Done! (Oh well, my assertion in #3 is wrong and so I will just have to manually check to see if my error happens to be a *Error.)

The moral of the story is that if your struct returns an error, return an error and not something else that implements the error interface. However, sometimes the journey is more important than the destination. You can't fully understand the language if you don't truly understand how the language handles interfaces and pointers.

No comments:

Post a Comment