Published on

How to Do X in Go

Authors

I have worked with Go in the past but I have not worked with it in some time. As a result, I forgot some of the conventions and ways of doing things.

To make it easier in future for myself and anyone else starting with Go (coming from another language) I have made this living document.

Environment Setup

A good Go version manager can be found here.

Using gvm simplifies the below as gvm will set up and manage the required go environment variables for you.

Gvm also greatly simplifies working with multiple versions of Go at the same time.

GOBIN

This is the folder where GO places binaries that have been built or/and installed. Some programs may need the full absolute path but using the $GOPATH command line variable should work most of the time. In your .zshrc or .bashrc export the GOBIN path as follows:

export GOBIN=$GOPATH/bin

Add GOBIN to your path so you can see binaries from there on your path:

export PATH=$PATH:$GOBIN

VS Code - Disable Annoying Linting Warnings For No Comments

Add the following to your VS Code settings.json file:

  "go.lintFlags": [
	  "--exclude=\"\bexported \\w+ (\\S*['.]*)([a-zA-Z'.*]*) should have comment or be unexported\b\""
  ],

Go path

export GOPATH=$HOME/code/go

In my case this is where it is, you can create this anywhere you want.

The GOPATH is where dependencies that you go get are saved to (it is sort of like your .m2/repo folder with Java and Maven).

The root of my GOPATH has the following in it (this will change greatly depending on the dependencies you have gotten):

bin/
overlay/
src/

Go root

This is the equivalent of Java's JAVA_HOME. gvm set mine to something like the following /Users/someuser/.gvm/gos/go1.12.7

My GOROOT has the following files in it:

AUTHORS
CONTRIBUTING.md
CONTRIBUTORS
LICENSE
PATENTS
README.md
VERSION
api/
bin/
doc/
favicon.ico
lib/
manifest
misc/
pkg/
robots.txt
src/
test/

IDE

VS code together with the official VS Code Go extension is rock solid.

This extension will use go get to install the requirements you need and other things like the Go formatter and lindter which the extension uses to check and manage code in IDE windows.

In my case the following Go tools were installed by the plugin on installing a new version of Go:

Installing 9 tools at /path/to/your/username/go/bin
  go-outline
  go-symbols
  guru
  gorename
  dlv
  gocode-gomod
  godef
  goreturns
  golint

Installing github.com/mdempsky/gocode SUCCEEDED
Installing github.com/uudashr/gopkgs/cmd/gopkgs SUCCEEDED
Installing github.com/ramya-rao-a/go-outline SUCCEEDED
Installing github.com/acroca/go-symbols SUCCEEDED
Installing golang.org/x/tools/cmd/guru SUCCEEDED
Installing golang.org/x/tools/cmd/gorename SUCCEEDED
Installing github.com/go-delve/delve/cmd/dlv SUCCEEDED
Installing github.com/stamblerre/gocode SUCCEEDED
Installing github.com/rogpeppe/godef SUCCEEDED
Installing github.com/sqs/goreturns SUCCEEDED
Installing golang.org/x/lint/golint SUCCEEDED

All tools successfully installed. You're ready to Go :).

Setting up VS Code to Use GVM

Do what is described here

Setting up a new project

Follow the last step here (i.e. the gvm part)

Cheatsheet

Definitions:

  • Language: This is enforced by the language.
  • Convention: This is the idiomatic Go way of doing this.
CategoryAreaLanguage/ConventionShort Example / Description
Scope (all types)publicLanguagefunc APublicFunction(){}, note how it starts with an uppercase letter.
privateLanguagefunc aPrivateFunction() {}, note how it starts with a lowercase letter.
protectedNot ApplicableSee this answer.
NamingFiles - single word nameConventionfoo.go
Files - multi word nameConventionfoo_bar.go
Files - Target a PlatformConventionfoo_windows.go, foo_linux.go
Files - Indicate to Go build to ignore the fileConvention_file_to_ignore.go
VariablesConventionfoo, fooBar
MethodsConventionfoo, fooBar
ConstantsLanguageconst foo = 10000 (only string, character, boolean and numeric values are supported)
Classes (structs in Go)ConventionAPublicStruct, aPrivateStruct, foo
VariablesBasic TypesLanguageint, bool, string, float
List/ArrayLanguageInline myList := []int {1, 7} or var myList [4]int and then myList[0]=1
Map/Dictionary - init and assignLanguagevar m map[string]int, init it m = make(map[string]int) and assigning it later m["one"]=1
Map/Dictionary - delete entryLanguagedelete(m, "one")
Map/Dictionary - safely get valueLanguagei, doesExist := m["one"] then check if doesExist is true before working with i
TupleNot applicable
EnumLanguage/ Conventionconst (NORTH = 0, SOUTH)
dataclassNot applicable
constantsLanguageconst fieldName := "FIRST_NAME". See here for details
null / nillLanguagenil. Be sure to check what the zero value is for your type. Most are not nil in Go
casting (only for interface to struct)Languagex.(T). See here
top level objectLanguageinterface in a function it would be func blah(arg interface{}) {
pass by valueLanguagefoo.someFunc(*bar)
pass by referenceLanguagefoo.someFunc(&bar)
pass by value/referenceLanguagefoo.someFunc(bar), this depends on how bar was defined.
receive referenceLanguagefunc SomeFunc(foo *Bar) {
receive valueLanguagefunc SomeFunc(foo Bar) {
initializeLanguagefoo := "bar"
reassignLanguagefoo = "baz" (this has to have been initialized first before this)
aliasConventionThis is where variables are pushed to higher packages so they can be referred to more easily
Default/Zero ValuesintLanguage0
boolLanguagefalse
stringLanguage""
float64Language0
Checking for niltimeLanguagesomeTime.IsZero(). Go's Zero value for time is 0001-01-01 00:00:00 +0000 UTC
intLanguagesomeInt == 0
boolLanguagesomeBool == false
stringLanguagesomeString == ""
floatLanguagesomeFloat == 0
structLanguageSee here for approaches
Functional CodingMapNot applicableGo is not a functional language, use a standard for loop.
ReduceNot applicableGo is not a functional language, use a standard for loop.
FilterNot applicableGo is not a functional language, use a standard for loop.
LoopingFor - vanillaLanguagefor i := 1; i <= 10; i++ {
For - over arrayLanguagefor i, v := rang yourArr {, see here for more
WhileLanguageUse for as described here
ClassesConstructorConventionfunc NewYourStruct(param1 string) YourStruct { }, this is basically a factory function
ToStringLanguageImplement the Stringable interface method func String() string
Static VariableLanguagevar i = 1 at global scope as described here
Class MethodLanguagefunc (m MyStruct) SomeStructMethod() { use m to refer to instance fields/methods
Static MethodLanguageSimply define a func in the global scope and do not attach to a struct
Organising CodePackageLanguageAt the top of your file package foo where foo is generally the folder the file is in
Main MethodLanguageIn the main package, in a file called main.go has a method func main() {.
external importLanguageimport ("github.com/reponame/module") where module is used to refer the package.
aliased/named importLanguageimport (foo "github.com/reponame/module") where we can now use foo for the package.
local importLanguageimport ("github.com/reponame/module") the url refers to your repo.
Organising Code Top Level Package NamesxConventionThis seems to hold experimental code
typesConventionThis is where you define the types used in your project
binConventionThis is where binaries are held
cmdConventionThis is where code that compiles to a binary is held. This is normally a main.go file.
InheritanceNoneLanguagetype Person struct { Name string }
SingleLanguageGo uses composition for inheritance see here
MultipleLanguageGo uses composition for inheritance see here
Error handlingCustom Error CreationConventionerrors.New("Whatever error message you want")
Custom Error UsageLanguage / ConventionSimply return an error object with the normal result. There's an error if err != nil
InheritanceInterface / Abstract ClassLanguageSee here
MiscellaneousiotaLanguageThis is a numeric universal counter used in enum/const block declarations
_Language / ConventionThis is used to indicate a value that is not used for example in destructuring
_ in enumLanguage / ConventionThis is used to auto-increment the first value of that enum
OperatorsTernaryLanguageUse if/else there is no ternary operator
String ManipulationuppercaseLanguagestrings.ToUpper("fOo") outputs FOO
lowercaseLanguagestrings.ToLower("fOo") outputs foo
titlecaseLanguagestrings.ToTitle("fOo bAR") outputs Foo Bar
literalsLanguagefmt.Sprintf("hello %s %s", name, surname). Verbs
Gotchascant load package import cycle not allowedLanguageImport circularly reference each other see here
PatternsmustConventionfunc MustFoo() string {, panics if an err is encountered inside MustFoo's method body.

Enum

type Compass int

const (
  NORTH Compass = iota +1,
  _
  SOUTH,
  EAST,
  WEST
)

var direction Compass

direction = SOUTH
  • iota+1 followed by _: This will automatically assign SOUTH to the int value 2, EAST to 3 and WEST to 4.
    • You need the _ if you want to start at 1 instead of 0 otherwise you can leave out the _ and SOUTH would then be 1 and NORTH 0.
    • _ is used to force iota to increment without assigning it to anything. This means you can have multiple _ to force it to increment further.
    • iota + 1 controls how incrementing will work, in this case, it is saying increment by 1 each time. iota * 2 would increment iota by 1 and multiply by 2 each time a new enum is assigned.
    • Assigning the first value to something than just iota is an easy way to verify if the enum has been initialized. If the enum has not been initialized it will have a value 0.
  • iota: Used to auto increment the int value for the other enums.
  • type Compass int: Assigning this only to the first type and not the other types explicitly will automatically make SOUTH, EAST and WEST of type compass.

To get the value of an enum you need to cast/coerse it e.g. int(NORTH)

  • To understand iota better see here.
  • To understand this enum syntax better see here.
  • For a detailed article on iota and enums see here

Output Enum as String Value in JSON Output

You need to implement the following 2 methods on the enum type:


func (c Compass) MarshalJSON() ([]byte, error) {
  // ...
}

// And
func (c *Compass) UnmarshalJSON(b []byte) error {
  // ...
}

For the Compass example I would add the following:


func (c Compass) String() string {
      switch c {
        case NORTH:
            return "North"
        case SOUTH:
            return "South"
        case EAST:
            return "East"
        case WEST:
            return "West"
      }
      return ""
}

var stringToCompass = map[string]Compass {
  "NORTH": NORTH,
  "SOUTH": SOUTH,
  "EAST": EAST,
  "WEST": WEST,
}

func (c Compass) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString(`"`)
	buffer.WriteString(c.String())
	buffer.WriteString(`"`)
	return buffer.Bytes(), nil
}

func (c *Compass) UnmarshalJSON(b []byte) error {
	var j string
	err := json.Unmarshal(b, &j)
	if err != nil {
		return err
	}
	*c = stringToCompass(j)
	return nil
}

To this gist for another example of doing this.

Interface / Inheritance

Golang does inheritance through composition. So for example if I have the following struct, interface and implementation:

type Person interface {
  Greet() string
}

type Student struct {
  Name string
}

func (s Student) Greet() string {
  return fmt.Sprintf("Hello %s", s.Name)
}

Now if I have another type called say Professional which I want to inherit everything from Student I do so as follows:

type Professional struct {
  Student
  YearsExperience uint
}

Anything that is of type Professional now has access to Students methods as below:

student := Student {
  Name: "John",
}

professional := Professional{
		Student: Student{Name: "Sally"},
}

fmt.Println(student.Greet())
fmt.Println(professional.Greet())

The full code for this is below:

package main

import (
	"fmt"
)

type Person interface {
	Greet() string
}

type Student struct {
	Name string
}

func (s Student) Greet() string {
	return fmt.Sprintf("Hello %s", s.Name)
}

type Professional struct {
	Student
	YearsExperience uint
}

func main() {
	student := Student{
		Name: "John",
	}

	professional := Professional{
		Student: Student{Name: "Sally"},
	}

	fmt.Println(student.Greet())
	fmt.Println(professional.Greet())
}

Which outputs:

Hello John
Hello Sally

See this playground to play around with this.

Pass By...

See this gist to understand the different ways of using pass by reference and pass by value.

Patterns

Must

This is the convention of prefixing method names of methods that panic on errors encountered as opposed to returning the error.

For example the none Must version of some validation method:

func ValidateBar() (string, error) {
  // ...
  err := validateSomeField()

  if err != nil {
    return "", err
  }
  // ...
  // ...
  // ...

  return someStringVar, nil
}

The Must version of this is:

func MustValidateBar() string {
  // ...
  err := validateSomeField()

  if err != nil {
    panic(err)
  }
  // ...
  // ...
  // ...

  return someStringVar
}