Go for a Rubyist

28 Apr 2014

Coming from the Rubyland and world of iOS, learning Go was an interesting experience. I've thought it might be worth to share some things learnt on the way, and put up the simple transition guide for everyone thinking about giving Go a shot.

This post is not the most idiomatic Ruby or Go you'll read, but it's instead written in a way to point similarities between the two.

Some of the reasons for Go

What's the reason people want to develop applications in Go? Here are some of mine:

  1. Deployment
  2. Performance
  3. Tooling
  4. It's not that complicated

Ruby is a wonderful language. I love writing it and there's thousands of people that do as well. What I don't fancy with Ruby is deploying. Deploying your own web apps is something you can still deal with, but shipping a tool to many people is a hassle. They need to have the correct Ruby installed, all the gems fetched, and often you need to support Rubies down to 1.8.7.

I highly encourage you to check out ngrok. After you download it and get your mind blown, just think of this: you've downloaded a tunneling proxy server, that ships with HTML frontend and bootstrap. In one binary. No dependencies. You've just run it without installing anything.

Go has built-in testing, code coverage, basic dependency fetcher, go get, unified code formatter, go fmt / go imports, cross-compiler, and many other tools. You can compile Go from your Mac for all the platforms, including Windows. Most of Go's tools are standalone CLI apps, that can give any editor some serious IDE capabilities.

Finally, Go is a way smaller language than Ruby or ObjectiveC, so there's less things to learn. Once you understand the basics, you'll be able to read mostly all the Go code you find online. Let's take a look.

There's no objects

But don't worry; You can achieve lots with structs.

1
2
3
4
5
6
7
car = Car.new
car.doors = 4
car.make = "Ford"
car.model = "Focus"

car.honk
return car
1
2
3
4
5
6
7
8
9
10
car := new(Car)
car.Doors = 4
car.Make = "Ford"
car.Model = "Focus"

car.Honk()
return car

// Note: this is not idiomatic Go, neither Ruby.
// It's intentionally written this way to point out the similarities.

Functions

You can make Go look more Object-Oriented by adding functions to structs. The following usage example demonstrates so:

1
2
3
def run_car(car)
  car.engine.start
end
1
2
3
func runCar(car *Car) {
    car.Engine.Start()
}

Go methods could be a bit hard to read at first. But it's really easy to do so, if you consider that all the Go functions are written the same way:

  1. func keyword
  2. receiver (optional)
  3. function name
  4. input (optional)
  5. output (optional)

The simplest Go function doesn't declare input, output or a receiver.

1
2
3
func sayHello() {
    fmt.Println("Hello!")
}

If you want to achieve the Ruby example from above, you need to add the Start() function on the Engine (receiver)

1
2
3
4
//    Receiver        Name
func (engine *Engine) Start() {
    fmt.Println("WROOOM")
}

Now by nature, this function doesn't return anything. Let's say you want it to return an error if car doesn't start:

1
2
3
4
//    Receiver        Name    Output
func (engine *Engine) Start() error {
    // ... ommitted
}

Finally, let's say you want to delay starting by 2s

1
2
3
4
//    Receiver        Name  Input      Output
func (engine *Engine) Start(delay int) error {
    // ... ommitted
}

How do I declare a class?

As we've mentioned before, there's no classes and objects in Go, but there are structs. Let's try to declare that Car example from above, both in Ruby and Go.

In Ruby, we'll make Car and Engine classes, and define methods inside them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Engine
  def start
    puts "WROOM"
  end
end

class Car
  attr_accessor :engine

  def initialize
    @engine = Engine.new
  end
end

car = Car.new
car.engine.start
# => WROOM

In Go, we'll make CarEngine and Car structs, and define functions on them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type CarEngine struct{}
func (engine *CarEngine) Start() {
    fmt.Println("WROOM")
}


type Car struct {
    Engine: *CarEngine
}
func NewCar() *Car {
    car := &Car {
        Engine: &CarEngine{}
    }
    return car
}

car := NewCar()
car.Engine.Start()
// => WROOM

Private and Public

There's no such a private keyword, or public static void main() in Go. In fact, doing so is pretty simple: - private stuff starts with lowercase - public starts with uppercase

That applies on the following examples, respectively:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// public
func HandleError(e error) {

}

// private
func handleError(e error) {

}

type Car struct {
    Make string
    Model string

    nickname string // this is private
}

Where's my do...end block?

Go does have first class functions, and you can use them as such:

1
2
3
go_to_the_store do |products|
  puts "I HAS BUY #{products}"
end
1
2
3
goToTheStore(func(products *Products){
    fmt.PrintLn("I HAS BUY %v", products)
})

Casting

Some people get afraid of types. And I have to admit - it is a bit confusing after getting so comfortable with Ruby. Casting in Go looks (like everything else) like upside-down casting in e.g. Java or C#.

So if you want to convert a string to byte array (byte slice in Go, to be correct), you would type

1
2
porsche := "Porsche"
bytes := []byte(porsche)

Vice-Versa

1
2
bytes := []byte("Porsche")
porsche := string(bytes)

Notice how parenthesies positioning is different from Java or C#, (string)bytes vs string(bytes). The array [] notation goes on the opposite side as well. Instead of byte[], you type []byte.

Dealing with JSON

This was one of the biggest challenges when I've started writing a web service. The concept is mostly this:

  • You either receive some JSON from the request, and you want to make a real object out of it.
  • Or, you already have an object and want to render it as JSON
  • Or, you're dealing with a map (Hashmap) that's not a first-class object

Let's assume the JSON we'll try to work with looks like this:

1
2
3
4
5
6
7
8
9
{
    "first_name": "Marin",
    "last_name": "Usalj",
    "address": {
        "street_name": "Fake st",
        "street_number": 456,
        "city": "San Francisco"
    }
}

If you want to deserialize this into first-class models, you need 2 structs:

1
2
3
4
5
6
7
8
9
10
type Address struct {
    Street       string
    City         string
    StreetNumber int
}
type User struct {
    FirstName  string
    LastName   string
    Address    *Address
}

If you try to Unmarshal this directly, the User won't deserialize properly. To demonstrate the problem, we can go the other way - make a model programatically and Marshal it to JSON.

1
2
3
4
5
6
7
8
9
10
11
12
user := &User{
    FirstName: "Marin",
    LastName:  "Usalj",
    Address: &Address{
        Street:       "Fake st",
        City:         "San Francisco",
        StreetNumber: 456,
    },
}

jsonBytes, _ := json.Marshal(user)
fmt.Println("User: ", string(jsonBytes))
1
2
3
4
5
6
7
8
9
{
    "FirstName": "Marin",
    "LastName": "Usalj",
    "Address": {
        "Street": "Fake st",
        "StreetNumber": 456,
        "City": "San Francisco"
    }
}

If you take a look at the generated JSON, it's not the same as the first one; the fields have capitalized keys, so we need to map them. Don't get scared of backticks. They're something similar to Ruby's 'string', %Q(string) and many other ways you can generate a string. In this case, they allow you to use quotes for JSON keys.

1
2
3
4
5
6
7
8
9
10
type Address struct {
  Street       string `json:"street_name"`
  City         string `json:"city"`
  StreetNumber int    `json:"street_number"`
}
type User struct {
  FirstName string   `json:"first_name"`
  LastName  string   `json:"last_name"`
  Address   *Address `json:"address"`
}

Awesome, we're ready to go. Now serializing and deserializing JSON to structs works as expected. In the real life, you'll mostly be getting this JSON from a HTTP request. Here's a sample factory method that takes a request pointer and returns a new User.

1
2
3
4
5
6
7
8
9
10
11
func NewUserFromRequest(r *http.Request) *User {
    var user User

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&user)
    if err != nil {
        fmt.Println("CHAOS")
    }

    return &user
}

The last part is dealing with your own maps. This might be useful when you're doing some request processing and manual serialization. A real life example would be listening to web-hooks from many web services, and mapping them to an uniform interface.

We'll extract request parsing in another method that'll return a map[string]interface{}. This is the syntax for the most generic Hashmap in Go. Basically, it says keys are strings, and values are anything (strings, numbers, arrays, objects).

Then we'll manually construct the user, as if the mapping didn't exist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func NewUserFromRequest(r *http.Request) *User {
    userMap := MapFromRequest(r)
    addressMap := userMap["address"].(map[string]interface{})

    user := new(User)
    user.FirstName = userMap["first_name"].(string) // this is how you cast
    user.LastName = userMap["last_name"].(string)   // this is how you cast

    address := new(Address)
    address.StreetNumber = int(addressMap["street_number"].(float64))
    address.Street = addressMap["street_name"].(string)
    address.City = addressMap["city"].(string)

    user.Address = address
    return user
}

func MapFromRequest(r *http.Request) map[string]interface{} {
    var userMap map[string]interface{}

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&userMap)
    if err != nil {
        fmt.Println("CHAOS")
    }

    return &userMap
}

To wrap up, a more idiomatic way of constructing the user would be passing everyting into initializer instead of using the new(User) syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func NewUserFromRequest(r *http.Request) *User {
    userMap := MapFromRequest(r)
    addressMap := userMap["address"].(map[string]interface{})

  return &User{
      FirstName: userMap["first_name"].(string), // this is how you cast
      LastName:  userMap["last_name"].(string),  // this is how you cast
      Address: &Address{
          StreetNumber: int(addressMap["street_number"].(float64)),
          Street:       addressMap["street_name"].(string),
          City:         addressMap["city"].(string),
      },
  }
}

We need to Go deeper...

There's no real meta programming like in Ruby, but there are some tricks you can make using the reflect package and the fact Interfaces implementation is implicit. To get some ideas, you may want to check out these packages:

  • Martini - a Sinatra inspred tiny web framework
  • Revel - a bit bigger web framework like Rails or Play

  • Convey - BDD testing framework with live reload and web frontend

  • Ginkgo - yet another RSpec-style BDD framework

Vim

If you've really got this far - here's a bonus: I've mentioned that fancy tooling on the top of the post. There's some neat tools that'll make coding Go feel as you're writing a scripting language like Ruby.

The simplest solution ever is to add the following mapping in Vim - it lets you hit <leader> g and watch the current file execute.

1
map ,g :w\|:!go run %<cr>

If you install this plugin, it'll format your code properly before executing. It'll also check your syntax and fix imports. If you're using an auto-completion engine, it'll provide a full-fledged, context-based autocompletion!

And, if you're writing test-driven app, Convey will re-compile and re-run test each time you hit save.

If you're writing a webapp, gin from codegangsta will reload the server each time you save.

Pretty cool, huh?