Go for Rubyists

Dhaivat Pandya

go

The great folks over at Google have developed an awesome language called Go. At first glance, it seems like Ruby and Go are distant cousins, at best. However, their complementary skill sets provide a perfect match.

It is definitely worth any Rubyist’s time to take a look at Go as some of the innovations it brings to the table are quite enticing.

For me, Go was the missing link between C++ and Ruby. Especially when writing servers that need to be quick on their feet, I often chose C++ and found myself missing the niceties of Ruby. I would have liked to write Ruby instead, but even with recent, massive performance improvements, Ruby can’t cope.

Go fills this gap. It provides the feel of a dynamic language like Ruby or Python, but has performance comparable to many of its compiled relatives.

It also has some really unique features which we’ll look into in this article. Let’s jump in.

Why Go?

When writing a server, opening one thread per client as a means of concurrency (if all of that sounds like gibberish, that’s fine, keep reading) is terrible when need to handle lots of clients. The option is to use something called non-blocking IO (thumbs up to the Node crowd). But, even across Unix-y OSes (e.g. Linux, Mac OS X, etc.), the mechanism for handling non-blocking IO efficiently differs. Then, in addition to all of these complications, there’s C itself. I don’t have anything against C for embedded devices or where running speed trumps development time significantly. But, as a day to day language, C doesn’t fit the bill for me.

Go provides amazing concurrency primitives, a nice syntax, a well stocked default library and a quick compiler. It solves the problems I had with C (and also C++, to some extent). It is fun to use even when your codebase gets larger.

In this article, I’ll go over the basics of the language in a whirlwind tour, pointing to documentation often. The main focus is to highlight the innovative features of the language that make it so unique.

The Tedium

Go is meant not to surpise; it should come easily. Here’s the basic code layout:

package main

func main() {

}

The main function is called to start us out. Let’s try out a “Hello, world:”

package main

import "fmt"

func main() {
  fmt.Println("Hello, world!")
}

The module for doing printing and scanning is called “fmt” in Go. Unlike Ruby, it isn’t included by default, so we’ve added an “import” statement at the top of the file to include it. The method Println from the fmt module will print out the string we pass in and a newline character (akin to puts in Ruby). Notice that public methods in Go begin with a capitalized letter.

Some quick looping:

package main

import "fmt"

func main() {
  //the basic for loop
  for i:=1; i < 100; i++ {
    fmt.Println(i)
  }
}

Go and Ruby have a completely different idea of the for loop. The Go version is more or less similar to C. You define a variable, check a condition, and specify what to do at the end of one loop iteration (in this case, incrementing i). This is basically the only kind of loop that Golang defines. Fortunately, it is extremely versatile. For example, this is a forever loop:

for {
}

I would encourage you to check out some of the (documentation on for)[http://golang.org/doc/effective_go.html#for].

Notice that when we set the value of i within the for loop, we don’t use “=”, we use “:=” instead. Here’s an example of the difference:

package main

import "fmt"

func main() {
  //defines the variable a
  a := 5
  fmt.Println(a)

  //sets a different value to a
  a = 10
  fmt.Println(a)

  //another way to define a variable
  var b int
  b = 15
  fmt.Println(b)
}

In the first chunk of the main function, the variable a is both declared and assigned. In the second, the value is reassigned, so = is used. The reasoning is that Go is actually a statically typed language, unlike Ruby, which is dynamically typed. So, the compiler has to know where variables are declared and where their contents are simply replaced. This is made clear with the third portion of the main function, which explicitly declares and sets the value of a variable using the var keyword.

Finally, as a likeness to arrays in Ruby, there are slices in Go. They are of the type []type, where type represents the type of object we want slice to return. Instantiating them is a little odd:

package main

func main {
  ///this creates a slice of integers with length 15
  mySlice := make([]int, 15)
}

We have to use the make() call in order to make a slice.

That was likely a very quick tour of some of the most basic features of the Go; I’d rather spend more time on some interesting features rather than the basic syntax which is documented very well.

Let’s check out goroutines.

Goroutines

Writing concurrent code is hard. Writing concurrent network code is even harder. The problem is that traditional threads don’t scale well and they are extremely difficult to handle once you have a few of them running. The Go team set out to solve this issue, producing goroutines.

Essentially, goroutines are lightweight concurrency mechanisms that communicate with each other using constructs called channels. They are incredibly simple to use:

package main

import "fmt"

func wait() {
  //wait around with a forever loop
  for {
  }
}

func main() {
  go wait()
  fmt.Println("We didn't wait because it was called as a goroutine!")
}

We have the method wait which is supposed to be a forever loop. But, we are calling the method as go wait(), instead of just wait(). This tells Go that we want it called as a goroutine and runs it asynchronously! Running the program does not produce a forever loop since the loop runs in the background.

So, Go has concurrency built into language, i.e. it has concurrency primitives. But, what’s the point? Just not having to include a library or module doesn’t seem like that much of a big deal. But, goroutines are fundamentally different from threads because they are much more lightweight. Remember how you shouldn’t write servers that use one thread per client? With goroutines, the situation is different:

package main

import (
  "fmt"
  "net"
)

//notice that in the arguments, the name of
//the variable comes first, then comes the 
//type of the variable, just like in "var"
//declarations
func manageClient(conn net.Conn) {
  conn.Write([]byte("Hi!"))
  conn.Close()
  //do something with the client 
}

func main() {
  //we are creating a server her that listens
  //on port 1337. Notice that, similar to Ruby,
  //a method can have two return values (although
  //in Ruby, this would be an array instead)
  listener, err := net.Listen("tcp", ":1337")
  for {
    //accept a connection
    connection, _ := listener.Accept()
    go manageClient(connection)
  } 
}

Whoa. That might seem quite a bit more complicated, but the idea is very simple. Let’s break it down step by step.

Looking at the main function, start by a call to the net.Listen method, which returns two values (in Go, you’re not limited to one value, similar to Ruby’s array unpacking) the server connection and the error. Then, enter into the main loop of the server, where it just sits around and listens for requests with the server.Accept call. That call “hangs” until a client comes along to connect. Then, once we are connected, pass on the connection object into a manageClient, but call it as a goroutine! So, the server is ready to process the next client.

Finally, notice a couple things about the manageClient method. Firstly, in the arguments list, the name of the variable comes first, the type after. This is more or less a stylistic choice that the creators of Go have made; you won’t even notice it after a week or so with the language.

In the body of the method, write the “Hi!” message to the client and then close the socket.

So, with a couple of lines of code, we’ve written a reasonable foundation for a simple server. Without too much effort, you can turn this into an HTTP proxy (bonus points if you implement some caching). Goroutines allow us to do this. They actually aren’t just lightweight threads; there’s a lot of magic going on behind the scenes that makes it possible to write simple imperative code inside goroutines.

Channels

Goroutines by themselves are useful, but their utility is amplified massively with the concept of channels. These serve as a communication mechanism between goroutines as well as the main process. Let’s see a really quick example.

package main

import (
  "fmt"
)

var eventChannel chan int = make(chan int)

func sayHello() {
  fmt.Println("Hello, world!")

  //pass a message through the eventChannel
  //it doesn't matter *what* we actually send across
  eventChannel < - 1
}

func main() {

  //run a goroutine that says hello
  go sayHello()

  //read the eventChannel
  //this call blocks so it waits until sayHello()
  //is done
  <- eventChannel
}

We have a goroutine called sayHello that prints out a “Hello, world” message. But, notice the declaration of eventChannel. Essentially, we have declared a channel of integers. We can send stuff into this channel and other portions of the code can read from this channel, making it a communication method. In the sayHello method, the eventChannel < - 1 pushes the integer 1 into eventChannel. Then, in the main method we read from the eventChannel.

There’s an important point here: by default, reading from the channel blocks, so it keeps waiting until there’s something that can be read from the eventChannel.

Let’s look at something a little bit more complicated:

package main

import (
"fmt"
)

var logChannel chan string = make(chan string)

func loggingLoop() {
  for {
    //wait for a message to arrive
    msg := < - logChannel

    //log the msg
    fmt.Println(msg)
  }
}

func main() {
  go loggingLoop()

  //do some stuff here
  logChannel <- "messaged to be logged"
  //do other stuff here
}

Here, we’ve defined a main loop that hangs around listening to events, i.e. loggingLoop. When it receives a message to be logged from the loggingChannel, it prints it out. This is a pretty common design, especially when there is some state to be held within the event loop.

So, with a few lines of code we’ve established communication between the main function and the goroutines. Shared memory has traditionally been a nightmare for developers because of issues such as locking, race conditions, etc. With Go, the concept of channels greatly reduces the chance of most traditional issues. Additionally, Go’s channels are an inherent part of the language; not just something tacked on with a library.

In comparison to Ruby, Go’s goroutines actually run in the background with the “default” implementation of the language (MRI Ruby runs entirely within a single thread so it cannot provide “real” parrallelism). Secondly, Ruby comes with a threading implementation, but using it is no easy task. In fact, the Agent library attempts to get some of the niceties of goroutines into the Ruby world.

Wrapping It Up (for now)

We’ve covered a lot of ground in this article. We ran through the very, very basic syntax of Go code and then quickly dived into concurrency mechanisms.

Stay tuned for Part 2, where we’ll be going into some more depth with syntax and discuss some other awesome features that Go gives us.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • mb0

    either use var mySlice = make([]int, 15)
    or mySlice := make([]int, 15)
    also do not busy wait with an empty for instead use an empty select {}

    • dhaivatpandya

      Fixed the first bit. As for the second, for the given example, just using “for{}” is easier since it is more familiar and (specific to the given case and context) does not create a problem.

  • dhaivatpandya

    Nice catch. Fixed it.

  • Benny Ng

    nice written, worth reading. would be great if you can elaborate the essence of lightweight thread behind the scene.

    • dhaivatpandya

      Thank you for the compliments. The lightweight goroutines are actually multiplexed into multiple OS threads. Calling them lightweight threads doesn’t *really* work, because they aren’t really threads (though they do use them underneath).

      • Benny Ng

        got it. thanks!