Skip to content

Instantly share code, notes, and snippets.

@jimarnold
Created November 5, 2012 22:35
Show Gist options
  • Save jimarnold/4020852 to your computer and use it in GitHub Desktop.
Save jimarnold/4020852 to your computer and use it in GitHub Desktop.
Adventures in Go

Executive summary

Go is C with type-safety and garbage collection, and Java without all the Java. It does a fairly good job of getting out of your way, but it is, to me, a language of compromises. It is strongly, statically typed but lacks generics. It has useful built-in types but they act very differently to the types you can create for yourself. It wants to be a modern language but uses 40 year old syntax. The core library is a confusing mix of functions-that-act-on-values and methods-on-types.

That said, I have enjoyed using it. It's compiled, but there is very little compiler wrangling due to the way your source files must be structured, and external dependencies are resolved from source, not from linker arguments. It's fast. It feels familiar. The concurrency and functional aspects are great. Would I recommend it to a client? Not yet - Java and C# are far more mature and Go doesn't offer anything compelling for the types of programs you might use those languages for (it has been said of Go that it is a language for writing servers). But for personal projects, this might be my new jam.

Language notes:

The interesting bits

Type inference:

i := 123
f := 1.23
s := "hello"

Maps are built in:

m := map[string]int {"a":1,"b":2,"c":3}

(curiously, no type inference there).

Concurrency gets some love:

go func() {
  fmt.Printline("I'm on a different thread (possibly)!")
}()

and made more interesting by first-class support for synchronized communication via channels:

queue := make(chan int)
go func() {
  for {
    queue <- 0
  }   
}() 

//print 0 for eternity
for i := range queue {
  fmt.Print(i)
}

Go programs are fairly low overhead:

package main
import "fmt"
func main() {
  fmt.Println("hello world")
}

There is out-of-the-box package management:

go get github.com/go-gl/glfw

The basics

Go is a simple language. The syntax is mostly C-like, with a few notable differences:

var greeting string = "hello"

The type comes after the variable name (BKSHHHH!!! mind blown, right?). Looks a bit arbitrary at first, but it helps to line up variable names, and you get used to it quickly. The name is, after all, the most important attribute.

Most of the time, however, you can use type inference:

greeting := "hello"

:= is a short-form assignment, and the compiler will figure out the type. You can only use the short form inside functions.

Functions

This is a function:

func add(x, y int) int {
  return x + y
}

Note the compressed syntax for declaring x and y as ints, and the return type after the arg list. You have probably also noted the lack of semi-colons at the end of lines by now.

Functions can return multiple values:

func getTwoThings() (int, int) {
  return 1, 2
}

Functions can be anonymous:

add := func(x, y int) int {
  return x + y
}
four := add(2, 2)

Functions are also closures; they have access to the state of the scope they were declared within.

Loops

The only looping construct in Go is for. This is your bread and butter for loop:

for i := 0; i < 10; i++ {

}

This is an infinite loop:

for {

}

You can use the range keyword as an iterator:

myArray := []int{1,2,3}
for _,element := range myArray {
  fmt.Println(element)
}

I appreciate the desire to keep the language small, but this seems a little awkward to me. As a C# programmer I would miss the foreach keyword, and as a Ruby programmer I would be looking to emulate block syntax with functions (which turns out to be tricky in Go due to static typing and no generics).

Structs

Apart from the built-in types (variously sized numerics, boolean, string) you can create structs:

type Vector struct {
  x float64
  y float64
}

A function can be associated with a struct type:

func (v *Vector) Length() float64 {
  math.Sqrt((v.x * v.x) + (v.y * v.y))
}

which effectively turns it into a method:

v := Vector {1, 2}
l := v.Length()

Personally I would have preferred to be able to declare methods within the struct itself, instead of tying them together with a reference.

I introduced a few other elements in the above code, so let me back up a little:

import "math"
s := math.Sqrt(1.0)

The math namespace comes from an imported package. Importing a package makes its exported types and functions available to you. Packages are go's unit of compilation, and they form part of the language's rather opinionated, enforced, program structure. I won't attempt to explain it here but you can read more at http://golang.org/doc/code.html

v := Vector {1, 2}

This is a struct literal declaration. It constructs a Vector, assigning the parameters within braces to the vector's fields in the order they are declared. You can omit parameters by using names:

v := Vector {y:2} //v.x == 0

Hmm. The C programmers among you are wondering how this Vector gets allocated. Is v a pointer? How did v.x get initialized?

v1 := new(Vector)
v2 := &Vector{}

These two declarations are equivalent. In the first case, the new operator returns a pointer to a zeroed instance of the type. In the second case, we take the address of the struct literal directly, but it is functionally equivalent to using new. In both cases, all fields are safe to access and will have their default zero values.

v3 := Vector{}

v3 is a value, not a pointer. Go's pointers lie somewhere between C and C# - you can pass values by copying, or you can pass pointers to values. No pointer arithmetic is allowed. Memory is reclaimed by garbage collection.

Structs can implement interfaces:

type Zombie interface {
  Attack(target *Human)
}
type SlowZombie struct {
}
type FastZombie struct {
}
func(z *FastZombie) Attack(target *Human) {
}
func(z *SlowZombie) Attack(target *Human) {
}

You can also create types from functions, or by aliasing existing types (this is similar to typedefs in C)

type MyString string
type Sorter func(elements []MyString) (sorted []MyString)

which allows some nifty higher-order functional wrangling, as functions can be used as arguments or return values from other functions.

Arrays

fruit := [3]string {"apple", "orange", "banana"}

fruit is an array. Arrays are fixed-size, typed values. [3]string is a different type to [2]string, and the following code will fail to compile:

func thereShallBeThreeFruit(fruit [3]string) {

}
fruit := [2]string {"apple", "orange"}
thereShallBeThreeFruit(fruit)

which may or may not be a useful feature. #Slices Arrays become more interesting when fronted by a slice, which is go's answer to vectors and sequences. A slice is a reference to an array, with a length and a capacity. It can be resliced into smaller chunks, and these chunks will refer back to portions of the same array. Data can be appended to a slice, and a new underlying array will be allocated if the original capacity is exceeded.

fruit := []string {"apple", "orange"}

fruit is now a slice (note the length was omitted from the declaration - unlike arrays, length is not part of a slice's type definition).

Slices can also be allocated with the make keyword:

fruit := make([]string, 2)

make is used to allocate slices, maps and channels. It differs from new by initializing the instance (allocating the array and setting the length and capacity in the case of a slice), as opposed to just zeroing its fields. I'm not sure why the language designers decided to create two separate keywords here, instead of allowing constructors for types which are guaranteed to be called before the instance is returned. #Maps A map is a collection of associated keys and values.

infectionRegister := map[string]bool {"Bob":true,"Alice":false}
bobIsInfected := infectedRegister["Bob"] //Bob's infected - destroy his brain!

map has some associated functions - len, delete - but they are not treated as methods, so it's:

delete(infectionRegister, "Bob")

rather than

infectionRegister.delete("Bob")

which is a little annoying. #Channels Channels are go's built-in method of communication between threads of execution (or goroutines - more on them later). [To be continued]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment