I’ve spent the past few weeks learning the Go programming language, and working on a backend service with a REST API written in Go. In this post, I’d like to discuss my experience learning Go, from the perspective of someone who has previously programmed primarily in Java.

Both Go and Java are interesting and useful languages, and my intention here is not to advocate the use of one language over the other. Rather, I want to outline a number of Go’s features that I found interesting or noteworthy when I first learned about them:

Variable Declarations

To declare a variable in most languages - like Java - you must first specify the variable’s type, and then its name. For example, to declare an integer variable named foobar in Java, we’d do this:

int foobar;

But when declaring a variable in Go, you first specify the name, and then the type:

var foobar int

This is very awkward at first. Variable declaration is one of the most common things one does when programming, and declaring the type before the name was engrained into my mind by years of Java programming. But the Go approach becomes natural after awhile.

Go is statically typed, but it can perform type inference on local variable declarations. For example, I could create a string var named qux like so:

qux := "baz"

This is quite useful, and has no equivalent in Java. Note that this syntax is only valid for local variables.

Structs, Not Classes

No special constructor construct; Struct Literals The closest thing to a class in Go is a struct. I can define a simple struct like so:

type YogiBear struct {
  Intelligence string
}

I can then create an instance of YogiBear using a “struct literal”:

yogi := YogiBear {
  Intelligence: "smarter than average bear",
}

No Constructors

It sure would be convenient if I could set the default value of a YogiBear instance to "smarter than average", instead of hoping that the client knows this is the right value for the Intelligence field. But, this isn’t possible in Go.

There is no constructor construct in the Go language. Sure, it is possible to create a factory function that will initialize and return a struct, but you can’t force a client to use it. This approach assumes that the client, will know that you need to use the factory function (or know when it is safe not to).

This can be a bit annoying at first, especially coming from Java where much emphasis is put on creating proper constructors and ensuring objects are initialized in a valid state. But it is futile to try to use a language with a preconceived notion of how it should work. You cannot force a language to work as you expect it to, and Go is no exception. It is certainly possible to write good code without using constructors, it just takes time and practice.

In this case, I could use a factory function. Or, an alternative approach would be to define a const containing the default value of a YogiBear’s intelligence.

Implicit Interfaces

Go supports interfaces, much like Java. The difference is in how you implement (aka satisfy) them. For example, suppose I have an interface called Shape, with a method to obtain the perimeter. In Java, I would need to create a concrete type and explicitly declare that it implemented the Shape interface. This might look something like:

class Square implements Shape {
    /* ... */
    public double perimeter() {
      return this.side * 4;
    }
}

In Go, types satisfy interfaces implicitly. This means that all a type needs to do to satisfy an interface is have functions/methods with the required signatures. A concrete type that satisfies the Shape interface might look like this:

type Square struct { /* ... */ }

func (s Square) Perimeter() float64 {
    return s.side * 4;
}

Notice that we don’t have to write Square implements Shape as in Java. We just provide the appropriate method, perimeter - and that’s it! One common objection to this design is that is is technically possible to accidentally define a type that satisfies an interface without you having intended it to do so. This isn’t really a problem in practice, though, because even if it does happen, it is unlikely that you would somehow accidentally use your type as an implementor of said interface without wanting to.

Access Control

In Java, there are four access modifiers: public, protected, private, and package-private. These are denoted with keywords placed at the start of a new member definition.

public int foo;  // publicly accessible
private int bar; // privately accessible

In addition to requiring an explicit keyword, all these modifiers can be somewhat confusing (see this). And in my experience, only public and private are actually used in practice.

Go takes a different, and characteristically simpler, approach. Go supports two “access modifiers”: exported and unexported. They work at the package level, meaning that functions, variables, and other types can be accessible only within their packages (unexported), or outside of them as well (exported).

Keywords are not required to denote whether a variable or type is to be exported or not. Instead, Go looks at the capitalization of the first letter of the variable or type’s name. Lowercase means unexported, uppercase means exported:

Foo int; // exported - accessible outside package
bar int; // unexported - only accessible within package

This approach is simpler (and more succinct!).

No Inheritance

Most “Object Oriented” programming languages support two different mechanisms for defining relationships between new and existing types: inheritance and composition, “is a” and “has a” relationships.

Inheritance allows new types to inherit properties from existing types, and to serve a role in their place. It allows one to design “object hierarchies”, in an attempt to mirror real-world structures and relationships. The classic example involves vehicles: A sedan is a vehicle, and a truck is a vehicle. But a sedan is not a truck, and a vehicle is not necessarily a sedan (or truck).

Composition allows new types to “have”, or to “use” existing types. But, unlike inheritance, it does not allow a new type to serve in place of an existing type if it “has”/”uses” the existing type. The classic example here involves vehicles as well: A sedan has a steering wheel, and a truck has a steering wheel. But sedans and trucks are not steering wheels.

Go is officially considered to be a Object Oriented language, but it does not support inheritance, just composition. At first glance, this can seem extremely limiting, but Go’s interface typing in many ways alleviates the need for inheritance. Go also has a very interesting feature called “Embedded Types”, which is a sort of improved composition mechanism:

Embedded Types

Go supports type embedding, which allows you to include an anonymous field in a struct, like so:

type SteeringWheel struct {}
type Sedan struct {
  SteeringWheel // Notice this field has no name - it is anonymous
}

This is very useful, because as a result, it allows Sedan to access all of SteeringWheel’s methods. For example,

func (s *SteeringWheel) Turn() {
  /* ... */
}

sedan := Sedan {
  SteeringWheel{},
}

// This is perfectly legal
sedan.Turn()

Note, however, that this is not inheritance in disguise, because it does not allow Sedan to be used in place of SteeringWheel:

func OnlyAcceptsSteeringWheels(wheel SteeringWheel) {}

// Not legal
OnlyAcceptsSteeringWheels(sedan)

// But we CAN do this
OnlyAcceptsSteeringWheels(sedan.SteeringWheel) 

Typed Primitives

In Java, primitive types are just that: basic types that can’t be extended in any way. Java does not allow me to create a method for ints. There is a sort of workaround for this in wrapper classes. Java has wrappers for all primitive types (e.g. Integer & Boolean), and it is trivial to create your own.

But in Go, we can do something rather interesting - we can create types primitives:

type Fahrenheit int
type Celsius int

I can now create typed int values! This is useful if I want to be very clear that a function is only supposed to accept values representing temperature on the Fahrenheit scale, say. Of course, it is trivial to cast an untyped int to either of these new types, Fahrenheit or Celsius, but that requires an explicit action to be taken on the part of the programmer, and won’t happen by mistake. As an example:

var boiling Fahrenheit = 212 // Could also perform a cast but this is much nicer

func FtoC(f Fahrenheit) Celsius { // Emphasis return value is in Celsius scale
  return (f - 32)*5 / 9 // Can use normal arithmetic operators on typed primitives
}

It is even possible to define methods on these types:

func (f Fahrenheit) ToCelsius() Celsius {
  return (f - 32)*5 / 9
}

The idea of created typed primitives was very foreign and awkward to me when I first encountered them. But it turns out to be very natural and useful. And, of course, typing isn’t limited to primitives. It is trivial to create new typed errors:

type PeasMixedWithCarrotsErr error

Functions & Methods

Go supports functions and methods. Functions work just as you would expect. They take some parameters and produce a result. Methods, however, take parameters and a particular receiver type. Methods are really just functions that require a receiver. For example,

type Dog struct {}

// Methods have a receiver, and can also have a pointer
func (d *Dog) Bark(n int) {
  for i := 0; i < n; i++ {
    fmt.Println("Bark");
  }
}

func main() {
  // Make a dog bark 5 times
  var dog Dog
  dog.Bark(5)
}

Functions are first class types in Go. This means that we can pass them around just as you would expect with any other type. So I can do this:

func Hi() {
  fmt.Println("Hi!")
}

// Prints "Hi"
func main() {
  h := Hi
  h()
}

What we have really just done is separated the selection and the call of a function (Hi) into two distinct steps. Because this is possible, and because methods are really just a special type of function, we can do some neat things to shapeshift a method such that we can use it like a typical function. Here is an example of a method value:

// Uses previous code from dog example
// Barks 5 times
func main() {
  dog := Dog{}
  b := dog.Bark // "Select" the Bark method with dog as its receiver
  b(5) // Don't have to specify receiver again, just pass in the param
}

This is particularly useful if we need to pass a method as a func param:

// "Does" the func `n` times
func Do(f func(), n int) {
  for i := 0; i < n; i++ {
    f()
  }
}

func (d *Dog) Jump() {
  fmt.Println("Jump")
}

func main() {
  dog := Dog{}
  // Messy way
  Do(func() { dog.Jump() }, 5)

  // Clean way
  Do(dog.Jump, 5)
}

Go also supports method expression syntax. This allows us to specify a method’s receiver as the first value in the normal list of parameters, rather than with dot notation. This can be useful if you do not know which receiver you want at compile time.

// Method call with "method expression" syntax
func main() {
  dog := Dog{}
  b := (*Dog).Bark // method expression 
  b(&dog, 5)
}

Multiple Return Values

Functions can return multiple values in Go (This is how errors are returned - see next section). For example, I can compute the sum and the product of two values in a function, and return both results (note that this isn’t necessarily a good use case, but it does the job of demonstrating how it works):

func SumAndProduct(x, y int) (int, int) {
  return (x + y), (x * y)
}
// Outputs "Sum: 5, Product: 6"
func main() {
  sum, product := SumAndProduct(2, 3)
  fmt.Printf("Sum: %d, Product:%d", sum, product)
}

Defer statements

There is a rather unique construct in go called the “defer statement”. It allows you to specify logic that you wish to have executed after the function has returned. This is especially useful when working with multiple goroutines. Here is an example:

func HelloWorld() {
  defer fmt.Println("World")
  fmt.Print("Hello ")
}

Invoking HelloWorld() will cause the test Hello World (not WorldHello !) to be displayed on stdout. This works because of the defer statement, which caused the fmt.Println("World") to be executed only after the function has returned, which occurs after fmt.Println("Hello ") has been executed.

Methods for Functions

Methods are just functions with a receiver parameter. Parameters are just instances of types. Functions are types. This means that it is possible to create a method for a function. This was very strange to me when I first learned of it. It looks something like this:

type FuncType func(param string)

var FuncTypeVar FuncType = func(param string) {
	fmt.Println(param)
}

func (ft FuncType) executeMe() {
	ft("Executing... 1... 2... 3...")
}

func main() {
	FuncTypeVar.executeMe()
}

Output:

Executing... 1... 2... 3...

I haven’t had any occasion to use this in practice, but it certainly is interesting.

No Exceptions

Many languages support an exception mechanism whereby a function can indicate that something has gone wrong and throw an exception up the call stack. You are usually forced to write code to deal with the exception, or else have your function’s execution be terminated if one is thrown to (at?) it.

Go doesn’t support exceptions. Well, that is not entirely true. You can panic, but this mechanism is reserved for special cases, and not routine errors that are more or less expected to occur during a program’s execution (the typical example of an “expected” error is an I/O failure). It is also not possible to catch a panic as you typically can do with exceptions (caveat: you can kind of do this using recover within a defered function, but it still isn’t quite the same).

Instead of exceptions, Go has errors. These are just types that satisfy the error interface - nothing special. They are returned from functions just as any other value would be, but they ought to be used to indicate, well, errors that occured during a function’s execution. You can capture the return value in a variable, traditionally called err, and check to see if it is nil. If not, then you should do something in response. As an example, consider opening a file:

file, err := os.Open("/super/cool/file")
if err != nil {
  log.Println("Failed to open file.")
}

Notice that it would have been entirely possible to have simply ignored the error value returned by os.Open and not written the if statement at all. Go does not force you to check for errors. You have to make sure to capture all error values returned by functions that you call, and be responsible enough to handle them properly.

Programming without exceptions can certainly feel a bit quirky at first, as is the case with many of Go’s defining features. The decision of Go’s designers to not support exceptions wasn’t made lightly. It is certainly one of the main points of controversy around the Go language. My own experience is that it can be a bit more pleasant, and often makes code easier to read, as it doesn’t split up your code like try-catch blocks do. Though, it can be cumbersome when you’re working on a block of code that can generate a lot of errors - such as IO operations - because you have to address each error individually, rather than dealing with them as a whole, as you could with a try-catch block.

Arrays/Slices

Go’s arrays are fixed length sequences of a type. They are unlike arrays in most other languages because their length must be constant. Therefore, this is invalid:

var numElements int = 21
// Produces error: non-constant array bound numElements 
var arr [numElements]string

But both of these array declarations are fine:

const numElements = 21
var arr [numElements]string // Doesn't produce error
var arrTwo [21]string       // Also doesn't produce error

Because their size must be known at compile time, Go arrays are used very infrequently. Far more common, however, are slices. Slices are basically pointers to arrays. This means that if the underlying array of a pointer is filled, then a new array can be allocated, the elements can be copied over, and the pointer (aka slice) can be pointed at the new array. So they are essentially resizable arrays; similar to an ArrayList in Java. Slices are created with the built-in make function:

var initialNumElements int = 21
// Create int slice with 21 elements to start with, but can be grown later
imASlice := make([]int, initialNumElements)

Succinct Concurrency

Great support for concurrent programming was one of Go’s primary design goals. Threads, or goroutines as they are called in Go lingo, are built right into the language itself, along with supporting mechanisms. They are not merely new classes created as afterthoughts as in Java or C/C++.

Creating a new goroutine is as simple as prefixing a function call with the go keyword:

go doAThing() // Runs func doAThing in new goroutine/thread

The brevity of this syntax stands in stark contrast to Java, where this is about as succinct as it gets:

new Thread(() -> doAThing()).start(); // Runs method doAThing in new thread

Go’s concurrency design is based on the CSC (Communicating Sequential Processes) model. There is a saying in the Go community that aptly describes how Go’s designers intended Go’s excellent concurrency features to be used:

Do not communicate by sharing memory; instead, share memory by communicating.

Go has channels to facilitate this communication between goroutines. Channels are essentially blocking queues for specific types. The following program illustrates the use of channels, and prints the integers 0-21:

func main() {
	// Make channel & waitgroup
	pipeline := make(chan int)
	var wg sync.WaitGroup

	// Launch producer & consumer in new goroutines
	wg.Add(2) // Set wg counter to 2
	go producer(pipeline, &wg)
	go consumer(pipeline, &wg)

	// Wait for producer & consumer goroutines to finish before exiting
	wg.Wait() // Blocks until wg counter is 0
}

// Send integers 0-21 to specified channel
func producer(out chan<- int, wg *sync.WaitGroup) {
	defer wg.Done() // Decrement wg counter when finished
	for i := 0; i <= 21; i++ {
		out <- i // Send i to channel for reception by consumer goroutine
	}
	close(out)
}

// Get integers from specified channel & print them
func consumer(in <-chan int, wg *sync.WaitGroup) {
	defer wg.Done() // Decrement wg counter when finished
	for i := range in { // Keep receiving next val from channel until it is closed
		fmt.Println(i)
	}
}

A roughly equivalent version of this program is shown below in Java:

public class Main {

    public static void main(String[] args) {
        // Setup queue and threads
        BlockingQueue<Integer> bq = new LinkedBlockingDeque<>();
        Thread prod = new Thread(() -> producer(bq));
        Thread cons = new Thread(() -> consumer(bq));
        
        // Launch producer and consumer in new goroutines
        prod.start();
        cons.start();        
        
        // Wait for producer and consumer to finish before exiting
        try {
            prod.join();
            cons.join();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }

    // Send integers 0-21 to specified queue
    public static void producer(BlockingQueue<Integer> out) {
        try {
            for (int i = 0; i <= 21; i++) {
                out.put(i);
            }
            out.put(-1); // Send -1 to indicate we are finished
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }

    // Get integers from specified queue & print them
    public static void consumer(BlockingQueue<Integer> in) {
        try {
            int i;
            while ((i = in.take()) != -1) { // Stop once receive -1
                System.out.println(i);
            }
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

JSON/Serialization Support

It is extremely easy to serialize an object in Go. JSON format is the most common encoding method used in Go, in my experience, (though other formats, such as XML are also supports) so I’ll use that in my example (note that I’m neglecting to handle errors here):

type YogiBear struct {
	Intelligence string
}

func main() {
	// Create a YogiBear
	yogi := YogiBear{Intelligence: "Smarter than average"}
	fmt.Printf("This is yogi: %v\n", yogi)

	// Serialize him to the file system
	b, _ := json.Marshal(yogi)
	fmt.Printf("This is yogi's JSON encoding: %s\n", string(b))
	ioutil.WriteFile("yogi.json", b, os.ModePerm)

	// Reify him
	b, _ = ioutil.ReadFile("yogi.json")
	reifiedYogi := YogiBear{}
	json.Unmarshal(b, &reifiedYogi)
	fmt.Printf("This is reifiedYogi: %v\n", reifiedYogi)
}

Output:

This is yogi: {Smarter than average}
This is yogi's JSON encoding: {"Intelligence":"Smarter than average"}
This is reifiedYogi: {Smarter than average}

Web/HTTP Support

Go has immense support for web programming. This is not surprising since Google initiated the development of Go (though, it is an open source project), and Google is primarily a web services company. It would be futile to attempt to document all of Go’s web support here, but I would like to show a representative example: it is incredibly easy to start up a simple static file server in Go:

package main

import "net/http"

func main() {
    panic(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}

Run this, and you can access all documents located in /user/share/doc (of course, you can substitute this with any file system directory) via localhost:8080 on your machine. Stunningly simple… There really is no comparison to Java in this respect.

The Go Playground

Google hosts the Go Playground, which allows for easy testing of Go code in a REPL-like environment online. It is incredibly handy when first learning Go (and forever thereafter). It also allows you to create shareable links to code you write, e.g. https://play.golang.org/p/mY2rzovmf4. This allows you to embed code examples in documentation, thereby allowing users to experiment and run your code without ever leaving the docs, e.g. https://golang.org/pkg/io/ioutil/#ReadAll (click the “Example” link to view the playground snippet). It even has a virtual file system, so you can use file IO code within the playground.

Libraries and “External Code”

Java has JAR files. They allow you to write and compile your code, and create a single binary file out of it. You can share this JAR file with others, who can then use it to import your classes and use their public APIs to interact with them without needing their source code.

Go does not have a an equivalent mechanism. For better or worse, if you wish to publish a Go library, then you will also have to share the source code for it. The vendor directory of a project contains the source code of all its dependencies. This source is then compiled when you build your project, just like the code you wrote.

Summary

I think that Go is a very cool language. It has some quirky features, but they generally turn out to be quite useful once you grow accustomed to them. There is a large community of Go developers, which means it is possible to find libraries to accomplish most tasks, if the standard libraries won’t suffice. And when there aren’t native go packages available, it is always possible to create bindings for C/C++ code (and you’ll be hard pressed to find a task that somebody hasn’t already written C/C++ code to accomplish). Go is a pleasure to use, and I look forward to working with it more in the future!