Go

Some notes regarding the Go language. Some topics have graduated to their own page:

Setup

First things first. How to install the Go language in different OSes.

Fedora

You can either use dnf directly and simply run

$ sudo dnf install golang-bin

This might not install the latest and greatest. If you want to use the most recent version, download it directly from https://golang.org/doc/install#install. If applicable, delete any previous /usr/local/go directory with

$ sudo rm -rf /usr/local/go

Next, extract the archive file with

$ sudo tar -C /usr/local -xzf /home/foo/tmp/go-$VERSION.linux-amd64.tar.gz

And add /usr/local/go/bin to the $PATH.

macOS

The best way to perform an attended installation of Go in macOS is to simply download the installer from https://go.dev/dl/ and running it.

Language design

Go doesn’t have sets

The Go language, notoriously, does not have1 some common data structures like sets. There are two main reasons for that:

  1. +Go does not have generics2
  2. Go relies on you writing your own data structures, generally

Go lacks generics, which prevent writing a … well, generic and efficient set implementation. Also, writing your own (non-generic) set with maps is quite straight-forward.2 The usual structure for a type T is map[T]bool, where the key is the element and the value is just a placeholder. For instance, for a int set, we can add elements:

s := map[int]bool{1: true, 3: true}
s[1] = true // already presenvar t
s[2] = true // adds new element

Some other techniques for maps replacing sets:

Set union

set_1 := map[int]bool{1:true, 2:true, 3:false}
set_2 := map[int]bool{1:false, 2:false, 4:false}
set_union := map[int]bool{}

for k, _ := range set_1{
    set_union[k] = true
}
for k, _ := range set_2{
    set_union[k] = true
}
fmt.Println(set_union)

Set intersection

set_1 := map[int]bool{1:true, 2:true, 3:false}
set_2 := map[int]bool{1:false, 2:false, 4:false}
set_intersection := map[int]bool{}
for k,_ := range set_1 { 
  if set_2[k] {
    set_intersection[k] = true
  }
}
fmt.Println(set_intersection)

To convert a (map) set to an array:

array := make([]int, 0)
set_1 := map[int]bool{1:true, 2:true, 3:false}

for k := range set_1 {  
   array = append(array, k)  
}
fmt.Println(array)

CI

GitHub

A potential workflow for GitHub is to use GitHub Actions for Go. An example workflow file, .github/workflows/test.yml, which runs go test (see Testing in Go) and go vet is:

on: [push, pull_request\]
name: Test
jobs:
  test:
    strategy:
      matrix:
        go-version: [1.14.x, 1.15.x]
        os: [ubuntu-latest]
    runs-on: ${{ matrix.os }}
    steps:
    - name: Install Go
      uses: actions/setup-go@v2
      with:
        go-version: ${{ matrix.go-version }}
    - name: Checkout code
      uses: actions/checkout@v2
    - name: Test
      run: go test ./...
	- name: Vet
	  run: go vet ./...

Containers

Minimal example

A minimal example of a Go container configuration for a web server running on port 8080:

# Start from the latest golang base image  
FROM golang:latest  
# Add Maintainer Info  
LABEL maintainer="Rui Vieira"  
# Set the Current Working Directory inside the container  
WORKDIR /app  
# Copy go mod and sum files  
COPY go.mod go.sum ./  
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed  
RUN go mod download  
# Copy the source from the current directory to the Working Directory inside the container  
COPY . .  
# Build the Go app  
RUN go build -o main .  
# Expose port 8080 to the outside world  
EXPOSE 8080  
# Command to run the executable  
CMD ["./main"]

Reference

Conversions

How to convert a string to byte array?

The conversion is simple:

 b := []byte("This is a string")

Collections

Sort map keys alphabetically

If a map contains string keys, i.e. var myMap map[string]T, we must sort the map keys independently. For instance:

keys := make([]string, 0)
for k, _ := range myMap {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, myMap[k])
}
Check for element

If we consider a collection, say, []string collection, the way to check for an element already present is, for instance:

func existsIn(needle string, haystack []string) bool {
    for _, element := range haystack {
        if element == needle {
            return true
        }
    }
    return false
}

Templates

Check if variable empty

In a Go template you check if a variable is empty by doing:

{{if .Items}}
<ul>
  {{range .Items}}
    <li>{{.Name}}</li>
  {{end}}
</ul>
{{end}}
Looping over a map

Looping over the map var data map[string]bool in a Go template:

{{range $index, $element := .}}
    {{$index}}: {{$element}}
{{end}}  

Processes

Executing external processes

Executing an external process and directing input and output to Stdout and Stderr.

 cmd := exec.Command("ls", "-1ao")
 cmd.Stdout = os.Stdout
 cmd.Stderr = os.Stderr
 err := cmd.Run()
 if err != nil {
	log.Fatalf("cmd.Run() failed with %s\\n", err)
 }

Testing in Go

Place the tests in your place of choosing, but keep the package declaration. Test functions should be parameterised as (t *testing.T) and start with the prefix Test, for instance:

package main

func TestFoo(t *testing.T) {
	value := Foo(5, 5)
	// ... assertions

The test files themselves must have the suffix *_test.go. Call the tests with go test.

Output

Printing struct keys

To print a struct along with its keys, we can use the Printf switch as in the official documentation. That is,

fmt.Printf("%+v\n", myStruct)

Date and time

Check if a date is empty

If a date is unassigned, the .IsZero() method can be used to check it

a := time.Time{}
a.IsZero() // This will be true

  1. As of the time of writing, that is Go 1.15. ↩︎

  2. Go indeed has generics starting with 1.18. ↩︎ ↩︎