Go is a new systems programming language launched by Google, and has received wide attention in the programming community. This article gives you an overview of this language, with some specific examples to help understand its features.
Go was announced just a few months back, but is already being touted as the next C language. Why?
The C language itself evolved from a language known as ‘B’. C was created in the 1970s, and still continues to be widely used; however, the language has mostly stopped evolving, and there is a dire need for a new language that could replace C. There are many languages that have been named ‘D’ (the most popular being the one by Walter Bright), or those that want to be the ‘D’ language. Still, nothing has made the cut, so far. Go might well become the next C language — or it may be “Gone” in a few years!
Is there substance behind the hype and buzz around Go? Yes, a lot! Most systems programmers (like me) find it very good after trying it out and writing non-trivial programs. Why?
- Go has a familiar C-like syntax (but not exactly C syntax, as we’ll see later)
- Yet, Go has the capabilities of modern dynamic languages like Python.
- Go is statically typed and almost as efficient as C (the performance of Go programs is within 10-20 per cent of the equivalent C code)!
- Though the language is designed for systems programming, it has capabilities like garbage collection and reflection, which make it a powerful language. Go is not an object-oriented language, but it has (arguably) novel features like interfaces, as we’ll see later in this article.
Go has already won Tiobe’s “Language of the Year Award 2009”. Tiobe is one of the most widely-referred-to programming language popularity indexes.
There is a lot to cover on Go, so I’ll be limiting myself to the most important aspects. First I’ll cover the essential background information, and then I’ll present some sample programs to introduce language features.
What is Go?
Go is a new, experimental, concurrent, garbage-collected, systems programming language.
- New and experimental: It is a new and evolving language that is still at the experimental stage. No production systems have yet been developed using Go.
- Concurrent: It is a concurrent language that supports “communication channels” based on Hoare’s Communicating Sequential Processes (CSP). The concurrency support is different from “lock-based” programming approaches like pthreads, Java locks, etc.
- Garbage-collected: Like most modern languages, Go is garbage-collected. However, work is under way to implement “low-latency” GC in Go.
- Systems-programming language: Like C, Go is a systems programming language that one can use to write things like compilers, Web servers, etc. However, we can also use it as a general-purpose programming language, for applications that create XML files, process data, etc.
Robert Griesemer, Ken Thompson (of Unix fame), and Rob Pike are the creators of the language.
Goals and motivation
Why was Go created? What are the problems that it tries to solve?
According to the creators of Go, no major systems programming language has come up in the last decade, though much has changed in the systems programming arena over the same period of time, or from even earlier. For example, libraries are becoming bigger with lots of dependencies; the Internet and networking are becoming pervasive, client-server systems and massive clusters are used today, and multi-core processors are becoming mainstream. In other words, the computing world around us is undergoing considerable change. Old systems programming languages like C and FORTRAN were not designed with these in mind, which raises the need for a new language.
Apart from these, the creators of Go felt that constructing software has become very slow. Complex and large programs have a huge number of dependencies; this makes compilation and linking painfully slow. The aim is for Go to be not just a language where we can write efficient programs, but also programs that will build quickly. Besides, object-oriented programming using inheritance hierarchies is not effective in solving problems — so the creators of Go want to have a better approach to write extensible programs.
Important characteristics
Look at some of the important features and characteristics of Go:
- Simplicity: Mainstream languages like C++, Java and C# are huge and bulky. In contrast, simplicity is a feature in Go’s clean and concise syntax. For example, C is infamous for its complex declaration syntax. Go uses implicit type inference, with which we can avoid explicitly declaring variables. When we want to declare them, the declaration syntax is simple and convenient, and is different from C.
- Duck typing: Go supports a form of “duck typing”, as in many dynamic languages. A struct can automatically implement an interface — this is a powerful and novel feature of Go.
- Goroutines: They are not the same as threads, coroutines, or processes. Communicating between goroutines is done using a feature known as “channels” (based on CSP), which is much safer and easier to use than the mainstream lock-based approach of pthreads or Java.
- Modern features: Go is a systems programming language, but it supports modern features like reflection, garbage collection, etc.
‘Hello world’ example
Here is the “hello world” example in Go:
package main import "fmt" func main() { fmt.Printf("Hello world!") }
All programs in Go should be in a package; it is main here. We import the fmt
package to use its Printf
function. Execution starts with the main.main()
function, so we need to implement it in the program. Functions are declared using the func
keyword. Note the lack of semicolons at the end of the lines in this program.
Looping examples
Here is a simple program that calculates the factorial of the number 5:
func main() { fact := 1 for i := 1; i <= 5; i++ { fact *= i } fmt.Printf("Factorial of 5 is %d", fact) }
Note that we haven’t declared the variables i
and fact. Since we have used the :=
operator, the compiler infers the type of these variables as int
based on the initialisation value (which is ‘1’ for i
).
The for
loop is the only looping construct supported in Go! If you want a while
loop, it is a variation of the for
loop in which the loop has only a condition check. For example:
fact := 1 i := 1 for i <= 5 { fact *= i; i++ }
Did you notice that we used a semi-colon in this code snippet? Wherever the compiler can infer semi-colons, we don’t have to use them — but sometimes we do. It will take a while to get used to the rules about semicolons in Go.
Looks like ‘C’, but it’s not ‘C’!
Here is a function for returning the square of a number:
func square(f float) float { return f * f }
This function definition looks very similar to C, but there are differences. For example, the argument is declared as f float
and not float f
as we would write in C. In Go, variable names are used first, followed by the type declaration. Similarly, the function’s return type is given after the parameter list; in C, we specify it before the function name. It takes time to get used to these differences, but this approach has resulted in considerably simpler declaration syntax than in C.
In Go, we can return more than one return value. This code segment gets the integer return value from Atoi
(a string to the integer conversion function in the strconv
package) function; it also gets the error value, which will be useful to check in case the function fails:
str := "10"; i, err := strconv.Atoi(str); print(i)
Multiple return values is a useful feature in Go, particularly for handling error conditions. This code also shows the built-in print function to print to the console — Go has a few built-in functions like this.
In Go, many features are similar to each other. We just covered multiple return values — can you now guess (given that i
and j
are integers) what this statement does?
j, i = i, j;
Yes, that’s right, it swaps the values in the variables i
and j
! Simple, concise and neat, right?
A function example
Here is a more difficult example using functions:
func transform(arr []int, foos []func (int) int ) { for _, foo := range foos { for i :=0; i < len(arr); i++ { arr[i] = foo(arr[i]) } } return } func main() { arr := []int{1, 2, 3, 4, 5}; foos := []func (int) int { func(arg int) int { return arg * arg }, func(arg int) int { return -arg } }; transform(arr, foos); fmt.Println(arr); }
We’ll start from the transform function. It takes two arguments: an array of integers, and an array of functions (in which each function accepts a single int
argument, and returns an int
). In the for
loop, we use the range keyword to get the indexes and values in the array of functions. Since we want only the value here, we ignore the index by assigning it to _
. Now, each value assigned to the foo
variable is a function, and we call that function for each element in the integer array, storing the function’s return value back in the same element of the integer array.
In the main function, we declare a slice (that internally points to an array), and a slice of functions. We create two functions without names (“function literals”), and put them in the slice. We pass these two variables to the transform function. The first function literal squares the integer value; the second function literal negates the value. So, this program prints: [-1 -4 -9 -16 -25].
Introducing structs and methods
Here is the declaration of a simple struct
named ‘Point’, with two integer members, ‘x’ and ‘y’:
type Point struct { x, y int; };
We can declare methods outside the struct
. Here is the Print
method, which prints the values in a Point
variable:
func (p Point) Print() { fmt.Printf("(x = %d, y = %d)", p.x, p.y); }
Method definition syntax is different from that of functions — a method definition takes a struct
type before its name, which is an implicit parameter during the call to that method.
Now, this code in the main function creates a Point
variable, and prints out its value:
p := Point{10, 20}; fmt.Printf("%v", p); p.Print()
It prints:
{10, 20} (x = 10, y = 20)
Here, Point{10, 20};
is a struct
literal, and looks like a constructor call. We use the %v
descriptor of the Printf
family of functions to print the struct
variable to console, followed by our custom Print
method’s output.
Introducing interfaces
We’ll build upon our Point
struct example by declaring a Printer interface, which declares a Print
method:
type Printer interface { Print() } type Point struct { x, y int } func (p Point) Print() { fmt.Printf("(x = %d, y = %d)", p.x, p.y); } func main() { var p Printer = Point{10, 20} p.Print() }
Interfaces are specified with the interface keyword. Note that these interfaces are not exactly the same as in C#/Java. The main difference is that a struct
doesn’t have to say it implements an interface — any struct
that implements the methods specified by an interface satisfies that interface! Those familiar with dynamic languages will easily understand that it is similar to “duck typing” in those languages. But remember Go is a strictly type-checking statically typed language, and it implements this feature!
A goroutines example
Goroutines are functions executing in parallel in the same address space in stack. They are not the same as threads or processes, so they get a new name, goroutines.
Goroutines communicate using “channels” (based on CSP). Here is an example as given in the “Effective Go” document (see the Resources section at the end of this article), which shows how a Sort on a big list can be done in parallel with some other computation.
c := make(chan int); // Allocate a channel. // Start the sort in a goroutine; when it completes, signal on the channel. go func() { list.Sort(); c <- 1; // Send a signal; value does not matter. }(); doSomethingForAWhile(); <-c; // Wait for sort to finish; discard sent value.
A “channel” is for communication between goroutines; this code creates a channel using the make built-in function (which is similar to new, and not covered in this article). A goroutine is invoked using the go keyword. Here, the goroutine is created to call a function defined as a function literal, which does two things. First, it does a Sort
on a list (assume that this is a time-consuming operation). Next, once the sorting is done, it sends a “signal” to the caller goroutine, that it is done with its work. Meanwhile, the caller code (which is a goroutine) does some other work in the call doSomethingForAWhile
, and waits for the other goroutine to finish in the statement <-c;
. This code segment effectively shows how goroutines are created and how communication is done between them.
Implementation status
Currently, Go implementations are available for two platforms: Linux and Mac OS X. There are two implementations for these platforms: one is a stand-alone implementation of a Go compiler and runtime, written by Ken Thompson. Another is a GCC front-end, implemented by Ian Lance Taylor. What about Windows? Some unofficial ports exist, but they are not very stable or up-to-date. I installed Go in Fedora, and the installation was smooth and easy.
All of Go’s toolset (compilers, packages, runtime libraries, tools, etc) is available as open source, under a BSD license.
Wrapping up
With my experience in trying Go over the last few months and writing non-trivial programs, I think there is a lot of substance behind the hype. Go certainly has some unique features that will make it a useful systems programming language, in this age of the Internet and multi-core processors. In just a couple of days since I started learning Go, I was able to write non-trivial programs, and began liking the language. It was fun learning and playing around with it. I would recommend Go to any systems programmer and encourage them to try it out.
Resources
- Go websites: The official website is www.golang.org (the Web server is implemented in Go!). Lots of material on Go is available from this website, including tutorials and presentations. You can download Go compilers and tools from this website. An unofficial Go website is at go-lang.cat-v.org — it has links to download the Windows version of Go, and has links to numerous Go-related resources.
- Learning Go: As of this writing, there is no book available on Go. However, the Go website has very good documents for learning Go. Check “Effective Go“. Robert Pike’s talk introducing Go is available on YouTube. Go’s discussion group is here.
- Try Go online: I find it really handy to compile and run Go programs online (!) at the IDEOne website.