Let’s Go! With the Go Programming Language

Let’s Go!
With the Go
Programming
Language

Programming Language

We’ll share our perspective on Go, the language of choice for Avenga products Couper.io and Wao.io.

In our other articles we analyzed other promising languages such as Julia, Rust and Lua.

The language called go

Jacek Chmiel, Director of Avenga Labs, shares about the Go language, its purpose as well as examples, along with some pros and cons.

Go is an open-source general purpose language which currently (March 2021) is language number 13 on the TIOBE index. The first release was in 2012, then in 2019 and 2020 there were two releases per year.

This language is clearly targeted at Linux, as most of its serious and more famous applications are running on top of various Linux distributions.

It also works on MacOS and Windows, but there are filesystem related problems with the latter.

The purpose of the language

Go language was created to address common problems with the C and C++ languages when writing server side services. It was designed for complex and reliable large projects utilizing multiprocessor and multicore architectures of modern data centers and workstations.

Go language is focused on code readability and is thus prepared for large teams working on Go projects together.

Go is always compiled to assembly and then to native binaries of the target operating system. It makes Go binaries very fast with no overhead of Just-In-Time compilers.

Go language is statically compiled, and the current version of standard libraries and other dependencies are included. Of course the price of that approach is the increased binary size, but in return there’s a guarantee it will work. There are virtually no problems with depending libraries or versions of dependencies that are so common in the cases of other languages.

Examples of applications

Very famous tools such as Docker, Kubernetes, Terraform, OpenShift, Cloudflare, Couchbase, InfluxDB and Soundcloud are developed in Go language.

→ Our take on the latest Kubernetes dropping Docker runtime support – what it means for enterprises

Our entire clouds, including native cloud environments, are running on runtimes and tools written in Go code. There’s no doubt about its usefulness. It’s not an exotic language despite being a relative niche, and it’s a great tool to have.

Our own API management tool and Backend For Frontend tool Couper.IO has been rewritten in Go language, so there’s no question that it can also be used to manage and create various APIs (i.e., REST, gRPC).

There are examples of Go applications for desktop using Qt and other GUI libraries. They are not very popular, but doable.

Applications that are written in such a fast and efficient language, as Go is, can be compiled to webasm and executed in any modern browser.

Easy to start

The philosophy behind Go programming can be described as Keep It Simple (KISS). Anyone with a background in C and C-derived programming languages (C++, Java, C#) should be able to learn Go language quickly.

It’s not to say that Go isn’t very different from them, as it is. The main difference is a very limited set of language features from one side and features baked directly into the language (like collections, maps, concurrency, arrays, slices, etc.) instead of adding them to external libraries (contrary to the majority of other languages).

Go comes with a great tutorial on their main webpage (https://tour.golang.org/welcome/1) which enables developers to discover language features interactively online.

Microsoft Visual Studio’s code has great language support for Go with plugins which are suggested automatically when opening .go files.(One of the Go source files from a Couper.io project).

Likes

  1. Static typing and strict type control – most of the errors can be detected at compile time without tedious debugging sessions.
  2. Declaring variables is made easier with an := operator for type inference (easier, but still well controlled).
  3. The order of magnitude is faster than Python applications, which is usually slower than the C, C++, and Rust applications. (https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go-python3.html )
  4. Much lower memory consumption than Python and similar to C, C++, and Rust.
    (https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go-python3.html )
  5. Functions are allowed to return multiple values (simple types and structs).
  6. Goroutines marked with the go keyword enable native concurrency for functions as it is built-in.
  7. Channels are language constructs for sending and receiving messages between different goroutines. They can also be used as synchronization tools.

Here is a fragment of code from a Couper product:

func TestHealth_ServeHTTP(t *testing.T) {
	type fields struct {
		path       string
		shutdownCh chan struct{}
	}

	tests := []struct {
		name       string
		fields     fields
		req        *http.Request
		wantStatus int
	}{
		{"healthy check", fields{shutdownCh: make(chan struct{})}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK},
		{"healthy check /w nil chan", fields{}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK},
		{"unhealthy check", fields{shutdownCh: make(chan struct{})}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusInternalServerError},
	}
  1. Functions enable functional programming as they are primary constructs.
  2. Go concurrency features are sometimes compared to a significantly more difficult language called Haskel. The features of Go language are limited in comparison, but they are much cleaner to read and easier to understand, and thus to learn.
  3. Many complex elements, such as templates, tons of dynamism, lambdas, etc. are missing which makes the code much simpler to read, albeit harder to write.
  4. Go is focused on the ease of reading code more than on how easy it is to write code. This comes from the experience of Google and other teams working in large teams and creating complex solutions. Readability comes first in the case of Go language application.
  5. Go removes unused objects using a garbage collector, but contrary to Java and other languages it is considered as very effective and with the much needed low latency.
  6. Go promises forward compatibility with newer versions of the language and libraries; code written in older versions is supposed to be recompiled into newer versions. Of course, there’s always a slight change and something won’t work but still, it’s one of the key promises of the Go team.
  7. It’s free and open sourced, and no licensing issues are expected at any time.
  8.  Go language is perfect for containers, as it has no memory and CPU hungry VMs and interpreters. It runs well on Docker and Kubernetes (Go vs Go).

Interesting facts

  1. There’s only one loop – its for can work as for and while depending on parameters (example below of a for loop from Couper.IO source code).
	for _, tc := range []testCase{
		{"name", "user", "pass", "", "Basic", "", false},
		{"name", "user", "", "", "Basic", "", false},
		{"name", "", "pass", "", "Basic", "", false},
		{"name", "", "", "", "Basic", "", false},
		{"name", "user", "pass", "testdata/htpasswd", "Basic", "", false},
		{"name", "john", "pass", "testdata/htpasswd", "Basic", "", false},
		{"name", "user", "pass", "file", "Basic", "open file: no such file or directory", true},
		{"name", "user", "pass", "testdata/htpasswd_err_invalid", "Basic", "basic auth ht parse error: invalidLine: testdata/htpasswd_err_invalid:1", true},
		{"name", "user", "pass", "testdata/htpasswd_err_too_long", "Basic", "basic auth ht parse error: lineTooLong: testdata/htpasswd_err_too_long:1", true},
		{"name", "user", "pass", "testdata/htpasswd_err_malformed", "Basic", `basic auth ht parse error: malformedPassword: testdata/htpasswd_err_malformed:1: user "foo"`, true},
		{"name", "user", "pass", "testdata/htpasswd_err_multi", "Basic", `basic auth ht parse error: multipleUser: testdata/htpasswd_err_multi:2: "foo"`, true},
		{"name", "user", "pass", "testdata/htpasswd_err_unsupported", "Basic", "basic auth ht parse error: notSupported: testdata/htpasswd_err_unsupported:1: unknown password algorithm", true},
	} {
  1. defer marks the code that will always be executed
func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
	return func(path string) ([]byte, error) {
		client := &http.Client{Timeout: timeout}
		req, err := http.NewRequest("GET", path, nil)
		if err != nil {
			return nil, err
		}
		resp, err := client.Do(req)
		defer func() {
			if resp != nil {
				if e := resp.Body.Close(); e != nil {
					log.Println(e)
				}
			}
		}()
		if err != nil {
			return nil, err
		}

		if resp.StatusCode != http.StatusOK {
			return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
		}

		return ioutil.ReadAll(resp.Body)
	}
}
  1. Semicolons ; are not necessary, except in rare scenarios
  2. Tabs are used instead of N spaces
  3. Capitalized names are a convention to mark functions that are to be exported from the package (Println from fmt package as an example)
  4. There’s no try, catch, finally, or no exceptions – seriously
  5. struct is used to define complex type
  6. There’s no class equivalent as data and operations are separated, in a more functional-esque style than an object oriented paradigm
  7. The Go team selected a gopher as the official mascot of the language
  8. Google created it, but they don’t own it and and it’s not a proprietary technology

Compiler focus

The Go compiler is quite strict and cautious, which is a pain for fast prototyping, but great for large projects.

For instance, if you declare variables and you don’t use them it’s a compiler error . . . not a warning, but an error!

func F() int {
	var f = 5  
	return 0
}

Go unboxing

After installing Go, there are multiple elements included out of the box: compiler, testing framework, build chain, lint for code formatting, and a static code analysis tool.

Command go get downloads external libraries by an URI; i.e.,
go get -u github.com/gogo/grpc-example

You can run the formatter to improve the code layout with the built-in command fmt
go fmt mysourcefile.go 

On the other hand, developers have voiced their disappointment with the lack of a more advanced package management system such as npm (from node) or pip from Python.

Good bye object orientation

Go has structs and functions, so there are no classes combining methods and encapsulating data. Go means no inheritance, no polymorphism, no virtual methods, no object constructors, etc.

However, simplicity and performance mean trade offs in language capabilities and richness.

Types can be derived (not inherited) from base types but only the value members will be there, not functions.  There’s no such thing as methods inherited from the base type.

package main

import (
	"fmt"
	"strconv"
)

type person struct {
	name      string
	birthYear int
	socialID  int
}

// function for Person type
func (p person) description() string {
	return ("Name: " + p.name + " born " + strconv.Itoa(p.birthYear) + " socialID=" + strconv.Itoa(p.socialID))
}

// type almost equal, almost means no access to "description" function from "base type"
type employee person

func main() {
	myPerson := person{name: "Jacek", birthYear: 1995, socialID: 23234234}
	fmt.Println("Person data: ", myPerson.description())

	var myEmployee employee
	myEmployee.name = "Jacek"
	myEmployee.birthYear = 1995
	myEmployee.socialID = 782834

	// will the method from other type work? NO! it won't compile
	fmt.Println("Employee data: ", myEmployee.description())
}

Go is focused solely on composition. Types can only contain primitive members of their own and other types.

Types can be embedded, as the example below shows:

package main

import (
	"fmt"
	"strconv"
)

type person struct {
	name      string
	birthYear int
	socialID  int
}
// this time we use composition of types
type employee struct {
	person
	employmentYear int
	salary         int
}

// function for Person type
func (p person) description() string {
	return ("Name: " + p.name + " born " + strconv.Itoa(p.birthYear) + " socialID=" + strconv.Itoa(p.socialID))
}

func main() {
	myPerson := person{name: "Jacek", birthYear: 1995, socialID: 23234234}
	fmt.Println("Person data: ", myPerson.description())

	var myEmployee employee
	myEmployee.name = "Jacek"
	myEmployee.birthYear = 1995
	myEmployee.socialID = 782834
	myEmployee.employmentYear = 2019
	myEmployee.salary = 1100

	// will the method from other types work? YES!
	fmt.Println("Employee data: ", myEmployee.description())
}

Please note that there is no implements keyword equivalent known from other languages. There’s no keyword at all.

Duck typing will match the implicit interfaces.

Go is a static type language; there’s reflection and some type of inference, but that’s basically it. No prototypes, dynamic classes, etc. Simplicity, again, is the key.

Go language is strongly criticized for lack of generics, but there are workarounds like empty interfaces. This is probably the biggest language change planned for version 2.

There’s no NULL problem, there’s a NIL problem

The eternal problem of no values and nulls, unfortunately, is not fixed in Go. NULL problems do apply to Go, which is kind of sad and disappointing because the language is much younger than its NULL-infested predecessors.

No exceptions

Instead of an exception there’s an error return type that carries the information about the improper execution of the routine. It requires writing code explicitly dealing with the errors, which could be annoying for newcomers, but it helps to maintain the discipline because errors cannot be simply ignored. Infamous empty try catch finally, etc. do not exist here.

Niche

The Go developer community is approximately two percent of the global population of developers; they are experienced and active. Still, it’s harder to find information online compared to Python, Java, JavaScript or C#.

Why go for the future of Wao.IO and Couper.IO?

Felix Hassert,  the product director of Wao.io and Couper.io at Avenga, highlights some key reasons for choosing Go for product development.

Go has a very good standard library, covering HTTP, TLS, networking and parsers as well. Those allow you to replace code clusters by a single line of code making coding lightweight and a lot more efficient. It’s a convenient and smart way of programming concurrency like C++ and Java, which is a viable benefit to leverage complex applications.

An interesting fact: two of the Go language co-inventors Robert “Rob” C. Pike and Kenneth “Ken” L. Thompson were a part of the original team who had developed Unix OS.

Looking further, it is fairly versatile and its offering of ease and the speed of the software development process, with simple functions-based syntax, is a major advantage of going with Go programming for server-side requirements. What is also worth mentioning is that Go applications are a suitable choice for a Docker container as fat binaries allow for slim Docker scratch images. All in all, though not without its faults, being pretty minimalistic, clean and readable makes it a language of choice for developers.

As to our recent Avenga experience, we switched to Go language for all custom-built components handling real-time usr traffic for our own product wao.io. Go offers high performance and good resource utilization on multi core systems, especially for IO and network bound tasks. The concurrency primitives give outstanding control over asynchronous processes.The standard library offers perfect support to programs on the network level and allows us to fine-tune the behavior of the HTTP and TLS stack. Last but not least, the built-in profiling tools helped us finding bottlenecks and optimizing.

wao.io is Avenga’s CDN platform aimed at speeding up and securing website delivery. It automatically improves the performance and security of the websites without any additional code changes.

Also, Go language has been a natural choice for us when developing another Avenga product, couper.io, because it was designed for concurrent server processing. Couper.io is used in many docker-based customer setups. The small footprint of the Go binary offers a well-controlled behavior in operations.

couper.io is Avenga’s API gateway, designed for decoupling the frontend from the backend by implementing the backend-for-frontend design pattern. It serves as a Web API and needs to implement a lot of Web standard protocols.

Summary

There’s a world outside Java, Python, JavaScript and C#. Go programming language is not expected to be an in-place replacement for all those enterprise languages but is a great choice when speed and a memory footprint are essential, and C/C++ complexities are to be avoided.

Yes, there is Rust which also addresses a similar target, but it is much more complex and harder to learn than Go.

Go is easier and built with the team collaboration in mind. Its code is readable and understandable. We appreciate its minimalism and letting go of almost all of the OOP legacy.

At Avenga, we have already written a new version of Couper.IO in Go and published the source code, which is proof of our commitment to this interesting language.

Other articles

or

Book a meeting

Zoom 30 min

or call us+1 (800) 917-0207

Start a conversation

We’d like to hear from you. Use the contact form below and we’ll get back to you shortly.