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 As of the time of writing, that is Go 1.15. some common data structures like sets. There are two main reasons for that:

  1. +Go does not have generics2 Go indeed has generics starting with 1.18.
  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.3 Go indeed has generics starting with 1.18. 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