Go for Rubyists
Note: Here’s another guest post from our friends at the Hybrid Group.
So you’re a Ruby developer and you’ve heard about this cool new language called Go, but you don’t know where to get started. You’ve read the bullet points and got a litle scared. Static typing? Compiling? Is this the 80’s all over again?! Well sorta! Go gives us the power of concurrent operations with a built in GC, so we get the power of a compiled language, but the lazyness of a dynamic language. Interested yet? Let’s get into the basics of the language and cut out all the uncessary exposition and look at some code.
#Hello world Here’s the canonical “Hello world” in Go.
package main
import "fmt"
func main(){
fmt.Println("Hello world!")
}
And its ruby equivalent
puts "Hello world!"
At first glance you may think “Wow that’s verbose!”, but if you have a c/c++/java background, this doesn’t look too out of the ordinary. Now let’s discect this program and firgure out what’s going on.
The package
keyword identifies the scope of the code you’re writing to the Go environment. Since we’re not writing a library, our package
has to be main
, so that Go can execute this code when you run your generated executable.
The import
function pulls in external packages to be used in your program. The fmt
package is the standard package used for formatted IO operations.
Now we get to the main()
function. If you’ve written a C program before, you know you’ll need a main()
function as the initial entry point for your programs execution. The main()
function in Go serves the same purpose. If you’re not writing a library for later use, you need a main()
function in order for your Go program to actually run and do something.
Finally we get to the point where are program actually does something! The line fmt.Println("Hello world!")
calls the fmt
package’s Println
function and prints Hello world!
to the console. Calling an external package’s functions follows the same format, package_name.function_name
.
That’s all there is to a Go program!
Variables
Variable definitions in Go are pretty much like every other programming language ever, with the exception that they come in two flavors.
There’s the implicit type instantiation
s := "Hello World!"
This creates a variable s
as a type string
and gives it the value Hello World!
. This is straightforward, but what if you want to create a variable for later use? Go has another way of defining variables.
var arr []int
for n := 0; n < 5; n++ {
arr = append(arr, n)
}
fmt.Println(arr)
This will create an int array which is scoped outside of the for
loop, so any changes to arr
within the for
loop persist in the scope of the function in which the for
loop resides.
The line arr = append(arr, n)
is calling the builtin append
function which takes an array, arr
, and appends n
to the provided array. It then returns a new array with contents of arr
with n
pushed on at the end.
Easy stuff, right?
Control Structures
Go only has 3 control structures, for
, if
and switch
. Wait a minute, only 3? Yes!
The if
statement is your standard fare
if true {
fmt.Prinln("I'm true")
} else {
fmt.Println("I'm false :(")
}
The for
statement on the other hand is much more interesting. Instead of having all sorts of while
, do
, loop
and each
loops, Go has one for
loop which allows a few different forms.
The first form is your standard “iterate until false”
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Another form is sorta like a while loop
b := true
i := 0
for b == true {
fmt.Println("doin some stuff")
i++
if i > 1 {
b = false
}
}
And the last form is like an each_with_index do
sorta thing
arr := []string{"a", "b", "c", "d", "e"}
for index, value := range arr {
fmt.Println(index, value)
}
which is ruby would be
["a", "b", "c", "d", "e"].each_with_index do |value, index|
puts "#{index} #{value}"
end
And last, there’s the switch
statement which is pretty much exactly how you would imagine it to be
i := 1
switch i {
case 1:
fmt.Println("its a 1!")
case 2:
fmt.Println("its a 2!")
}
There are more variations and nuances to these control structures, so be sure to consult the Go documentation on their specifics.
Functions
Functions in Go are first class. A first class function is a function which you can define and pass to another function for execution at a later time.
Let’s look at a function definition.
func MyFunc(a string, b int) bool
If you come from the c/c++/java world, this function defition may look backwards to you. The variables types are defined after the variable name with the return type at the end of the function signature. So this function accepts two parameters, one string one int and returns a bool.
Now what about closures? Go has got you covered there!
func (){
fmt.Println("some function stuff")
}()
Notice the extra ()
at the end of the function definition, that means that the function should execute immediately. Now what if you wan to pass a function and execute it at a latter time. Won’t that be interesting!
func ExecuteMyFunction(f func()) {
f()
}
func main {
f := func() {
fmt.Println("some function stuff")
}
ExecuteMyFunction(f)
}
In the above example I define a function and assign it to the variable f
. I then pass it to the function ExecuteMyFunction
which expects a variable of type func()
and then using the ()
on my variable, it is now executed.
Structs
Go isn’t an object oriented language. Instead they have the concept a struct and structs have functions attached to said struct. You then create an instance of that struct and then have access to that structs functions and variables. Here’s a simple Go program with an equivalent ruby example.
package main
import "fmt"
type Beer struct {
Brewery string
Name string
Style string
}
func (b Beer) IsTasty() bool {
if b.Style == "Light" {
return false
} else {
return true
}
}
func (b Beer) ToString() string {
return fmt.Sprintf("%v %v", b.Brewery, b.Name)
}
func main(){
b := Beer{"Spaten", "Optimator", "Doppelbock"}
if b.IsTasty() {
fmt.Println("I'll take a", b.ToString())
} else {
fmt.Println("I guess I'll have water....")
}
}
class Beer
def initialize(brewery, name, style)
@brewery = brewery
@name = name
@style = style
end
def tasty?
if @style == "Light"
false
else
true
end
end
def to_s
"#{@brewery} #{@name}"
end
end
b = Beer.new("Spaten", "Optimator", "Doppelbock")
if b.tasty?
puts "I'll take a #{b.to_s}"
else
puts "I guess I'll have water...."
end
In the Example above I define a struct with the syntax type Beer struct
. This creates a struct of type Beer
. Congratulations, you’ve just created a new type in Go! A struct definition has a list of variable names with their types, and to assign function to a struct you simple add a (b Beer)
in between the func
keyword and the name of the function. Think of the b
in (b Beer)
as the self
keyword. Any function with the (b Beer)
is only accessible to an instance of the Beer
struct.
You may be thinking “What about private methods and variables?” You’re absolutely right! The way Go defines private functions and variables is quite easy. All you need is a lower case letter at the beginning of the variable/function name and it’s now private!
type Person struct {
Name string
DOB string
ssn string
}
func (p Person) GetSSN() string {
return p.superSecretFunction()
}
func (p Person) superSecretFunction() string {
return p.ssn
}
As you can see in the example above the Person
struct has the private variable ssn
as well as the private function superSecretFunction
. Both ssn
and superSecretFunction
are only to available the Person
struct itself, so the user has no direct access to that information. Neat!
Enter the goroutine
Now we’re getting into the real power of Go. If you’ve tried to program concurrently in ruby, you have undoubtedly lost a few hairs. There are good libraries such as Celluloid for concurrent programming, but wouldn’t it be awesome if there were language primitives which did it all for you? Luckily Go has got you covered!
Go makes it so easy to do a concurrent operation, all it requires is the go
keyword in front of your function call, and you’re done. Super easy.
go ReallyLongAndExpensiveOperation()
Sure that looks easy enough, but how do you share data between goroutines in a safe way? Channles my friend, it’s all about goroutines and channels. Channels comes in two flavors, unbuffered and buffered. Unbuffered channels will block on writing to the channel, until the channel has been cleared. Whereas a buffered channel will queue up channel messages until you hit your defined buffer size, then it will block.
In this example I will call a function which which randomly sleeps for a duration, but I want to know how long my function slept for. Using an unbuffered channel, I can retrieve those sleep times just after they happen.
package main
import (
"fmt"
"time"
"math/rand"
)
func ReallyLongAndExpensiveOperation(channel chan int){
for {
rand.Seed(time.Now().UTC().UnixNano())
i := rand.Intn(1000)
time.Sleep( time.Duration(i) * time.Millisecond)
channel <- i
}
}
func main() {
channel := make(chan int)
go ReallyLongAndExpensiveOperation(channel)
for {
fmt.Println("I waited", <-channel, "milliseconds")
}
}
Wrap up
As you can see Go is a power language, especially for concurrent operations. If this quick overview has wet your appetite, you should head over to the tour of go which will go into more detail about the concepts I’ve outlined here.
Oh and do yourself a favor and run go fmt
on your project before you commit it to github. Go has fantastic tools to format your code with the Go standard indentation and white space practices.
Share your thoughts with @engineyard on Twitter