Skip to content

Instantly share code, notes, and snippets.

@adrianlshaw
Last active February 6, 2021 21:06
Show Gist options
  • Save adrianlshaw/f9c7554382a935f28455b706bc58943c to your computer and use it in GitHub Desktop.
Save adrianlshaw/f9c7554382a935f28455b706bc58943c to your computer and use it in GitHub Desktop.
Having fun with Go

Written for a friend.

Basic program:

sudo apt install golang-go

Make a file called main.go

package main

func main() {

}

Compile it using go build main.go. Produces a binary, which you can run by typing ./main.

Go is compiled, not interpreted, so it is very fast. But it is garbage collected, so it includes a small runtime within the binary.

Benefits of compiled Go binaries over Java JARs:

  • Completely self contained: don't need any JRE installed, literally don't need anything installed
  • Single binary contains everything it needs, so it makes it easy to share and deploy
  • Fast

Code formatting

There is only one way to format code

Run go fmt

And it will automatically format your code the one true way.

Importing

Every go file needs a package. A package is just a namespace. We've called the package the same name as the file.

Here is an import example. I've deliberately imported a module called "os" but I'm not using it.

package main

import (
    "fmt"
    "os"
)

func main() {

}

Go will tell you off if you import something you don't use. This forces you to only have code that you need and keeps binary sizes smaller.

Variable declaration and assignment

When assigning a value to a variable you use = as usual.

When declaring a new variable and assigning a value to it at the same time, you use :=.

You don't need to declare the type of the variable when declaring because this is established the first time you assign something to it. It then retains the same type for its lifetime, you can't change the type. So it is still a strongly typed system.

func main() {
	shell := os.Getenv("MY_SHELL_VARIABLE") // this declares a variable called shell and assigns it
	shell = "FOOBAR" // i can change the value like this
}

Go tells you off if you declare or assign variables that you don't use. This forces you to keep your code clean at all points in time.

If you really want to bypass this, set the variable name to _ which is you telling the compiler you don't care.

Adding external libraries to your project

In the terminal go mod manages your dependencies a bit like Maven except without the bullshit/nightmares.

go mod init myapplication

This just creates a file in our project called go.mod that will list our dependencies.

Now we can add import as much as we like. Here is an example program where we make a simple webserver. Notice the third party library mux that we use.

import (
	"fmt"
	"github.com/gorilla/mux"
)

func rootPage(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(`<html>Test page</html>`))
}

func main() {

	fmt.Println("Hello Yuri")
	router := mux.NewRouter()
	router.HandleFunc("/", rootPage).Methods("GET")
	err := http.ListenAndServe("80", router)
}

To make sure that go.mod is updated just run go mod tidy and it will automatically scan through our program to find any third party libraries and add them to our dependency list.

To fetch and build everything run go build without any arguments. It will automatically download dependencies and compile everything.

Function declaration

As you may have noticed above, one thing that is different about Go is that the type always comes after the variable name. This is just a small but annoying difference to get used to.

Function return values

As you may have noticed above, the keyword func is used where we usually put return type in Java. In Goland return type is put after parameters. This example has string as a return type:

func GetSerialNumber(orderID int) (string) {
	// body implementation
	return "FOOBAR"
}

In Golang you can also return two things instead of one, if you want to. Here we return a string and a return code that can be used for error handling.

func GetSerialNumber(orderID int) (string, err) {
	// body implementation
	return "FOOBAR", 0
}

This above example is typically useful if you want to keep return values completely separate from errors.

Generics

Go does not have generics. They may come in Go version 2. You can work around this by using interfaces, which work just like how interfaces do in Java.

Objects

Golang is not fully OOP because it does not support inheritance. If you want to mimic inheritance you have to use interfaces. However, inheritence is a bit overrated, so you might not miss it.

To make a simple object called Student with the method Print see below

package main
 
import "fmt"
 
type Student struct {
    Name string
    Roll int
}
 
func (s Student) Print() {
    fmt.Println(s)
}
 
func main() {
 
    jack := Student{"Jack", 123}
 
    jack.Print() // {Jack 123}
 
}

As you can see above, adding func (s Student) before the method name just goes to show that methods are simply just functions restricted to a particular namespace and variable scope.

Arraylists

Arraylists are called "slices" in Go. They are part of the language.

Example here: https://play.golang.org/p/HnQ30wOftb

You can also slice them up like in Python.

Maps

Go has in built language support for maps. No imports needed.

employeeSalary := make(map[string]int)
employeeSalary["Tom"] = 2000 //Adding a key value
val, exists := employeeSalary["Tom"]
if exists {
	fmt.Printf("Val: %d\n", val)
}

Pass by reference

By default Go is pass-by-value, just like Java. But Go also does support pass-by-reference, which is obviously wanted when you need good performance (by using pointers, no copies are made by the compiler).

func passByValue(item string) {
	item = "Foobar"
}

func passByReference(item *string) {
	*item = "Foobar" 
}

You don't have to worry about "freeing" references like in C/C++ because this is a garbage collected language.

Concurrency

In Java to have any form of concurrency you need to use threads. These can be quite bulky to work with due to the amount of boilerplate.

Go has a language concept for lightweight threads known as co-routines. These are called goroutines. You don't need any imports. To kick off a function call to run in the background, precede it with the keyword go

package main

import "fmt"
import "time"

func backgroundTask() {
	for i := 0; i < 3; i++ {
		fmt.Println("Hello from background task")
		time.Sleep(1 * time.Second)
	}
}

func main() {
	go backgroundTask()

	for i := 0; i < 3; i++ {
		fmt.Println("Hello from foreground task")
		time.Sleep(1 * time.Second)
	}
}

Arraylists (known as Channels)

package main

import "fmt"

type Channel struct {
  name string
}

func main() {
  var channels []Channel  // an empty list
  channels = append(channels, Channel{name:"some channel name"})
  channels = append(channels, Channel{name:"some other name"})
  
  // "%+v" prints fields names of structs along with values.
  fmt.Printf("%+v\n", channels)
  
  // or, create a list of a pre-determined length:
  ten_channels := make([]Channel, 10)
  for i := 0; i < 10; i++ {
    ten_channels[i].name = fmt.Sprintf("chan %d", i)
  }
  fmt.Println(ten_channels)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment