In Valid Logic

Endlessly expanding technology

Golang Oddity #1

Every language in existance has its own set of oddities. Since I’ve been working in Go full time for a couple of months now, I have run into some of its nuances and wanted to chronicle some of them.

While I am being critical in these kind of posts, the intent isn’t to bash Go, more it is about educating others. Go isn’t breaking existing convention (too much), however it is an emerging language and there isn’t as much out there to familiarize a newbie with things to be aware of.

So to start off with, give you something simple but very annoying:

Strings cannot be null, only empty

In Go, null (or nil) isn’t covered as heavily as I wish it was. Not every type is nilable, and this can lead to some annoyances.

All strings upon creation are simply an empty string (""). On the surface this doesn’t sound bad, but it can cause a lot of other busy work when dealing with other things that allow string to be null, or where the difference between null and empty are very important.

Most databases have understood for a long time that a null string and an empty string are completely different. Another is with user supplied input. Go is excellent for writing servers and APIs, and a common case with an API is CRUD functionality. You might want to support a partial update, where omitted values (essentially null) or not altered while supplied values (which may be a blank string) are updated.

Take the case of user supplied input, such as over a JSON API:

// try at http://play.golang.org/p/1A7XZva4C1
package main

import (
  "encoding/json"
  "fmt"
)

type Person struct {
  Name string `json:"name"`
  Location string `json:"location"`
}

func main() {
  var p Person;
  json.Unmarshal([]byte(`{"name":"John"}`), &p)
  fmt.Printf("Name: %q\nLocation: %q\n", p.Name, p.Location)
}
Name: "John"
Location: ""

In this case I define a struct and unmarshal some JSON that only specified the name. But then you can see Location is set to "". If they already have Name and Location set, and are doing an update with only Name, I don’t want to blank out Location. Now you got to do hoops.

Pick up that hula hoop… welcome casting

// try at http://play.golang.org/p/VlAJ4N9uGY
package main

import (
  "encoding/json"
  "fmt"
)

type Person struct {
  Name interface{} `json:"name"`
  Location interface{} `json:"location"`
}

func main() {
  var p Person;
  json.Unmarshal([]byte(`{"name":"John","location":"Gotham"}`), &p)

  fmt.Printf("Your zipcode is %d\n", lookupZipcode(p.Location.(string)))
}

func lookupZipcode(l string) int {
  switch l {
  case "Gotham": return 1
  case "Metropolis": return 2
  }
  return 3
}

In this mock example, passing the location to another function to look up the zip code, but it expects the location as a string, so now you need to cast it.

Uhh ohh, with interace{}, type enforcement isn’t inherent

  json.Unmarshal([]byte(`{"name":"John","location":1234}`), &p)
  ...
  fmt.Printf("Your zipcode is %d\n", lookupZipcode(p.Location.(string)))
panic: interface conversion: interface is float64, not string

goroutine 1 [running]:
main.main()
  /tmpfs/gosandbox-9aac7f9a_0c33fe58_a998effb_2a4a973a_458fb2a3/prog.go:17 +0xcd

However, when using interface{} as our type we lose the inherit type handling within json.Unmarshal and this will result in a panic rather than returning an error. In this case, the panic is on the print line rather than when unmarshaling. So now we need to do our own type validation, which has annoyances of its own.

Type checking #1

  switch p.Location.(type) {
  case string:
    fmt.Printf("Your zipcode is %d\n", lookupZipcode(p.Location.(string)))
  default:
    fmt.Println("OMG you didn't enter the right value")
  }

Type checking #2

  if s, ok := p.Location.(string); ok {
    fmt.Printf("Your zipcode is %d\n", lookupZipcode(s))
  } else {
    fmt.Println("OMG you didn't enter the right value")
  }

For type checking you can either go the switch route or check the second parameter in the cast call. If you’re morbid you could write your own typeof() using reflection perhaps.

For me, it is annoying because I need to actually do it and care. Perhaps I’m simply too spoiled by Ruby, however plenty of other languages support null strings as well. And so what if I am spoiled by Ruby… it focuses on developer happiness rather than hoops for performance. There are trade offs to all things, and I’ve accepted Go’s empty strings. I just miss my null strings.

Tuesday, October 16, 2012

 
blog comments powered by Disqus