Intro to Go for Java Developers

Unless you've been living under a rock, or deep in crunch mode for several years, you've likely heard of Go (AKA golang), Google's new-ish language. It was designed as an alternative to the growing complexity of C++, especially around concurrency. It's also attracting droves of Python developers, as it offers dramatically better performance, all the fun of type safety, and a syntax that's more comfortable than Java or C#.

But I like Java just fine

However, for us Java (and C#) developers, we're told every new language is the one that will save us from ourselves. Let's take a quick tour of Go and see what it offers.

To this end, I won't bore you with explaining the basics of programming. I will show you the key differences with Java, and why you might consider Go for your next project.

Playground

For all of the examples listed in this article, you'll see a link next to 'Play this' -- this refers to the Golang Playground. This is a quick and easy way to test out the language without installing anything.

Hello World

Of course, before we get started, here is the canonical 'Hello World' for Go:

package main

import "fmt"

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

Play this

This syntax is familiar to most developers in C-style languages.

Is it Object-Oriented? Functional? Procedural?

Go has constructs from all of these schools of thought, but with some modern best practices built in. For example, we've all heard these mantras before:

For this reason, Go has made some interesting choices. First off, it has no concept of "Objects" -- a single abstraction that represents both state and behavior. It just has the idea of Types -- in C-like structs:

type Address struct {
    Number string
    Street string
    City   string
    State  string
    Zip    string
}

Notice also that the types follow the declaration, and upper-cased letters are used to start identifiers.

So, this would almost seem like a purely procedural language. If you've used Scala or C#, however, you're probably familiar with the idea of Extension Methods. This is also possible in JavaScript (by modifying the object prototype), Groovy (by manipulating the metaclass), and Ruby (monkey-patching). Instead of having those as a separate concept, Go makes those the only way to define behavior for a type:

package main

import "fmt"

type Address struct {
    Number string
    Street string
    City   string
    State  string
    Zip    string
}

func (a Address) Location() {
    fmt.Println("I’m at", a.Number, a.Street, a.City, a.State, a.Zip)
}

func main() {
    address := Address{Number: "137", Street: "Park Lane", City: "Kirkland", State: "WA", Zip: "98033"}
    address.Location()
}

Play this

Notice some more neat things here. We have named constructor parameters. We did not provide a type to the variable 'address'. The pattern := tells the Go compiler to infer the type. And, the Location() function was automatically bound as a method on the Address type.

So, what would inheritance look like in this world? Let's create a MultiFamilyAddress:

type MultiFamilyAddress struct {
    Address Address
    Unit string
}

This is a perfect example of composition-over-inheritance but in Go. Now if we want to call the Location method, we have to do it like so:

func main() {
    address := Address {Number: "137", Street: "Park Lane", City: "Kirkland", State: "WA", Zip: "98033"}
    multi := MultiFamilyAddress {Address: address, Unit: "200"}
    multi.Address.Location()
}

Play this

Of course, we can always define a method with the signature func (m MultiFamilyAddress) Location() if we wanted to avoid this indirection. This isn't really inheritance the way we think of it. To do field-based inheritance, we use a construct Go calls anonymous fields:

type MultiFamilyAddress struct {
    Address
    Unit string
}

Not much different, right? This is Go's way of including all the fields of Address as though they were local fields on MultiFamilyAddress. This means the instantiation of MultiFamilyAddress will now look like this:

multi := MultiFamilyAddress{Address{Number: "137", Street: "Park Lane", City: "Kirkland", State: "WA", Zip: "98033"}, "200"}
multi.Location()

Play this

Go also offers interfaces, but they are a bit different than your normal OO interfaces. We'll cover those in another article.

So we've seen the procedural and object-oriented methodologies, but what about functional? A key component of functional programming Higher-order Functions. In Java, as of version 8, we can do something like this:

List<String> strings = Arrays.asList("Hello", "World");
strings.foreach(n -> System.out.println(n));

Of course, in Java 7 or before, it would be more like this:

List<String> strings = Arrays.asList("Hello", "World");
for ( String str : strings )
    System.out.println(str);

In Go, it would look something like this:

func main() {
    strings := [...]string{"Hello", "World"}
    for _, item := range strings {
        fmt.Println(item)
    }
}

Play this

Some interesting things here. First, to declare an array, we put that at the beginning of the variable definition. We used [...] in indicate the compiler should figure out the actual size. We could have easily made it [2]string{"Hello", "World"}.

The for loop is where it gets interesting. First, you see we are taking 2 parameters back, one indicated with an _ character. This is a convention in Go (and some other languages) for a parameter we don't care about. In this case, it's the index position of the element. The range operator takes a []T type, and executes the code inside the curly braces on each item.

Of course, this wasn't clearly a higher-order function, nor did it involve closures. Let's take a look at a simple example that does this:

func main() {
    x := 5
    fn := func() {
        fmt.Println("x is", x)
    }
    fn()
    x++
    fn()
}

Play this

This prints, as you might expect:

x is 5
x is 6

So we have functions as data types. This lets us do some interesting things:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

type calcOp func(int, int) int

func main() {
    // You seed your RNGs, right?
    rand.Seed(time.Now().Unix())

    fns := []calcOp{
        func(x, y int) int { return x + y },
        func(x, y int) int { return x - y },
        func(x, y int) int { return x * y },
        func(x, y int) int { return x / y },
        func(x, y int) int { return x % y },
    }

    fn := fns[rand.Intn(len(fns))]

    x, y := 171, 35
    fmt.Println(fn(x, y))
}

Play this

So what's going on here? First, we've defined a type called calcOp -- a calculator operation. It is a function that takes 2 integers, and returns an integer. This is now a defined type we can use in argument lists and objects.

In the main method, we create a collection of these objects. However, since we have ommitted a size, it's not an array. In Go parlance, this is called a Slice.

We instantiate this collection of calcOp functions. We pick one at random. We initialize x and y with 171 and 35 respectively (that multi-assign syntax is also a feature of Go), then execute the function with those values. Neat!

Concurrency Constructs

So now we've seen that Go encapsulates many existing programming schools, but if you're a fan of one of those in particular, there is almost certainly a better language for it. Haskell and OCaml for functional, Clojure and Ruby for OO, and C and Rust for procedural. One of the key selling points, and I cringe while typing this out, is that Go is meant for the cloud. Not only do we parallelize and distribute our applications, we need to parallelize our code as well. This has been a major source of both performance issues, and correctness issues.

To that end, Go has two constructs that are going to help us: goroutines and channels. Goroutines are a lot like actors (in the Akka Actor sense) -- basically multiple threads without necessarily having a 1-to-1 correlation to system threads. When one blocks, another takes over. Channels are a way to separate computation and provide a clean interface to talk between them. Let's take a look at what they do:

package main

import (
    "fmt"
    "math/rand"
    "time"
    "strconv"
)

func Announce(message string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()
}

func main() {
    for i := 0; i < 20; i++ {
        dur := time.Duration(rand.Int31n(10)) * time.Millisecond
        Announce("Item " + strconv.Itoa(i), dur)
    }

    fmt.Println("Done!")
}

Play this

The main method is just a bunch of setup -- defining dur to be a small duration of time (up to 10 milliseconds), and printing a value to the console 20 times. If you ran this program as-is, what would you expect to see? A bunch of random-ordered "Item X" messages, followed by a 'Done!' message? Here's what you actually get:

Done!
Program exited.

Wait, what? Let's look at that Announce function again. It is called with go func() -- this is how you invoke a goroutine. I am oversimplifying, but think of goroutines as backgrounded processes on the shell. Or, if you really know your threading model in Java, they are daemon threads. That is, they do not hold up program execution. When the main thread dies, they die as well. In Go, a goroutine will execute if the program is still running. We didn't get anything on the console because the program didn't run long enough. Let's add this line right before the 'Done!' line in the main function:

time.Sleep(time.Duration(5 * time.Second))

Play this

This tells our main thread to pause for 5 seconds, then we can continue and finish. With this model, we get our expected output:

Item 18
Item 15
Item 9
Item 5
Item 6
Item 17
...

So, that's goroutines. They're like background processes. The obvious question here is -- how do I make sure they execute? That is, you want to (potentially) offload the work to another thread or process, but it's important that it finishes. This is where Channels come in.

In Go, Channels -- blatantly taken from the link -- are "the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine."

Call this IPC or eventing or what have you. It is a basic construct of communicating between goroutines. So, what does a channel look like? To make a channel, we use the Go builtin make. It makes a variable for you, and it's how you make channels:

mychan := make(chan string)

chan is the identifier for a channel. The string identifier says it's a channel of strings. That is, it takes and emits strings. The simplest way to emit and receive messages is this:

go func() { mychan <- "ping" }()
msg := <-mychan
fmt.Println(msg)

Play this

We are using a goroutine lambda to emit a message to the channel mychan, and then receiving it into msg.

So, how would we apply this to the example above? We know we can send a message to a channel, and we know we can receive messages. Additionally, receiving a message is a blocking operation -- the execution stops until a message is available. We could go really naive with it:

func Announce(message string, delay time.Duration) {
    mychan := make(chan bool)

    go func() {
        time.Sleep(delay)
        fmt.Println(message)
        mychan <- true
    }()

    <-mychan
}

Play this

In this example, we receive from mychan after the execution of func finishes. This has one rather predictable side effect: all lines are printed in order. Because receiving a message is a blocking operation, we don't return control to the for loop until we have received a message. Now, what if we want to keep the parallelism? Here's how I solved this one:

package main

import (
    "fmt"
    "math/rand"
    "strconv"
    "time"
)

func Announce(message string, delay time.Duration, done chan bool) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
        done <- true
    }()
}

func main() {
    numMessages := 20

    channels := make([]chan bool, numMessages)

    for i := 0; i < numMessages; i++ {
        channels[i] = make(chan bool)
        dur := time.Duration(rand.Int31n(10)) * time.Millisecond
        Announce("Item "+strconv.Itoa(i), dur, channels[i])
    }

    for i := 0; i < numMessages; i++ {
        <-channels[i]
    }

    fmt.Println("Done!")
}

Play this

Here, we use the make function again to create an array of channels, one for each message. Then, inside the loop, we create a channel and stick it in the array. We then pass that channel to the Announce function. The goroutine inside that function signals the channel when it has executed. Because we don't query the channels until afterwards, this allows the random-order execution we're looking for. To finish it up, we drain the array of channels.

There are other problems with this solution -- what if we don't know the number of channels we want, what if the number is too large to reasonably store in memory? These will be left as an exercise for the reader.

Last Little Bits

So we've seen some neat concurrency concepts, as well as how to structure types and methods.

First, if you don't want to use the := syntax, you can declare a variable with a type:

var myint int = 5

This is not too useful for our examples. You can also declare constants:

const foo = "This is a constant"

We saw above that you can return multiple values from a function. You can do that yourself:

func mutireturn() (int, string) {
    return 42, "foo"
}
var x, str = multireturn()

We didn't show a pure example of higher-order functions in the functional section, so here's two of those:

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x // sum is declared outside, but still visible
        return sum
    }
}

func sum(i int) func(int) int {
    sum := i
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    add := adder()
    fmt.Println(add(3))
    fmt.Println(add(5))

    add2 := sum(2)
    fmt.Println(add2(0))
    fmt.Println(add2(3))
}

Play this

This gives us the output:

3
8
2
5

And one last bit. Go has a defined structure to the code. There is only one correct way to format your Go programs. It's so important, that there is a go format command to put your code in the correct style, and it's not configurable. Holy wars have been started over the correct way to align braces, spaces, and brackets in C-style languages. Go picked one and built it in. When you have one less thing to worry about, you can focus on more important concerns.

Final Thoughts

Go is quite a fun language to work with. It has a lot of the power of C/C++ (including pointers), but cuts out a lot of cruft. It can be run either as a pre-compiled unit, or you can run a single file on the command line with go run myprogram.go. This makes it serve dual purpose of compiled and interpreted software. This makes it just as appropriate for high-performance, long-running software as it does for advanced shell scripting. Happy programming!