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.

Tuesday, January 3, 2017

Non-spatial Tables

About a year ago, my Twitter timeline blew up. It turns out the Esri User Conference was going on and this got a lot of people chatting together. The root of the matter was a perception that Esri was not providing reasonable support for non-spatial data within GeoPackages. This was leading people towards flawed, clumsy workarounds like inventing spatial columns to tack onto attribute tables(!). Uh, folks, this is not what we had in mind...

I am not here to point fingers and the fact is that both sides had a point. A strict read of GeoPackage v1.1 did not allow non-spatial attribute values. However, in practice data providers routinely need to deliver data that does not contain geometry properties. We agreed that it was not reasonable to require any GeoPackage that contained non-spatial tables to be declared and documented as an "Extended GeoPackage". This does not promote interoperability. 

In response, we modified the standard by adding a new Attributes section that describes how to store non-spatial attribute tables in a GeoPackage. The new section is present in the on-line (working) copy of the specification and it will be incorporated in the next release (tentatively numbered GeoPackage 1.2). We hope that this addition will clarify things and encourage people to use GeoPackages as intended. We consider the change to be low-risk because it creates a new encoding option that would be ignored by previous versions of the standard.