- Published on
How to Do X in Go
- Authors
- Name
- Yair Mark
- @yairmark
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.
Category | Area | Language/Convention | Short Example / Description |
---|---|---|---|
Scope (all types) | public | Language | func APublicFunction(){} , note how it starts with an uppercase letter. |
private | Language | func aPrivateFunction() {} , note how it starts with a lowercase letter. | |
protected | Not Applicable | See this answer. | |
Naming | Files - single word name | Convention | foo.go |
Files - multi word name | Convention | foo_bar.go | |
Files - Target a Platform | Convention | foo_windows.go , foo_linux.go | |
Files - Indicate to Go build to ignore the file | Convention | _file_to_ignore.go | |
Variables | Convention | foo , fooBar | |
Methods | Convention | foo , fooBar | |
Constants | Language | const foo = 10000 (only string, character, boolean and numeric values are supported) | |
Classes (structs in Go) | Convention | APublicStruct , aPrivateStruct , foo | |
Variables | Basic Types | Language | int , bool , string , float |
List/Array | Language | Inline myList := []int {1, 7} or var myList [4]int and then myList[0]=1 | |
Map/Dictionary - init and assign | Language | var m map[string]int , init it m = make(map[string]int) and assigning it later m["one"]=1 | |
Map/Dictionary - delete entry | Language | delete(m, "one") | |
Map/Dictionary - safely get value | Language | i, doesExist := m["one"] then check if doesExist is true before working with i | |
Tuple | Not applicable | ||
Enum | Language/ Convention | const (NORTH = 0, SOUTH) | |
dataclass | Not applicable | ||
constants | Language | const fieldName := "FIRST_NAME" . See here for details | |
null / nill | Language | nil . Be sure to check what the zero value is for your type. Most are not nil in Go | |
casting (only for interface to struct) | Language | x.(T) . See here | |
top level object | Language | interface in a function it would be func blah(arg interface{}) { | |
pass by value | Language | foo.someFunc(*bar) | |
pass by reference | Language | foo.someFunc(&bar) | |
pass by value/reference | Language | foo.someFunc(bar) , this depends on how bar was defined. | |
receive reference | Language | func SomeFunc(foo *Bar) { | |
receive value | Language | func SomeFunc(foo Bar) { | |
initialize | Language | foo := "bar" | |
reassign | Language | foo = "baz" (this has to have been initialized first before this) | |
alias | Convention | This is where variables are pushed to higher packages so they can be referred to more easily | |
Default/Zero Values | int | Language | 0 |
bool | Language | false | |
string | Language | "" | |
float64 | Language | 0 | |
Checking for nil | time | Language | someTime.IsZero() . Go's Zero value for time is 0001-01-01 00:00:00 +0000 UTC |
int | Language | someInt == 0 | |
bool | Language | someBool == false | |
string | Language | someString == "" | |
float | Language | someFloat == 0 | |
struct | Language | See here for approaches | |
Functional Coding | Map | Not applicable | Go is not a functional language, use a standard for loop. |
Reduce | Not applicable | Go is not a functional language, use a standard for loop. | |
Filter | Not applicable | Go is not a functional language, use a standard for loop. | |
Looping | For - vanilla | Language | for i := 1; i <= 10; i++ { |
For - over array | Language | for i, v := rang yourArr { , see here for more | |
While | Language | Use for as described here | |
Classes | Constructor | Convention | func NewYourStruct(param1 string) YourStruct { } , this is basically a factory function |
ToString | Language | Implement the Stringable interface method func String() string | |
Static Variable | Language | var i = 1 at global scope as described here | |
Class Method | Language | func (m MyStruct) SomeStructMethod() { use m to refer to instance fields/methods | |
Static Method | Language | Simply define a func in the global scope and do not attach to a struct | |
Organising Code | Package | Language | At the top of your file package foo where foo is generally the folder the file is in |
Main Method | Language | In the main package, in a file called main.go has a method func main() { . | |
external import | Language | import ("github.com/reponame/module") where module is used to refer the package. | |
aliased/named import | Language | import (foo "github.com/reponame/module") where we can now use foo for the package. | |
local import | Language | import ("github.com/reponame/module") the url refers to your repo. | |
Organising Code Top Level Package Names | x | Convention | This seems to hold experimental code |
types | Convention | This is where you define the types used in your project | |
bin | Convention | This is where binaries are held | |
cmd | Convention | This is where code that compiles to a binary is held. This is normally a main.go file. | |
Inheritance | None | Language | type Person struct { Name string } |
Single | Language | Go uses composition for inheritance see here | |
Multiple | Language | Go uses composition for inheritance see here | |
Error handling | Custom Error Creation | Convention | errors.New("Whatever error message you want") |
Custom Error Usage | Language / Convention | Simply return an error object with the normal result. There's an error if err != nil | |
Inheritance | Interface / Abstract Class | Language | See here |
Miscellaneous | iota | Language | This is a numeric universal counter used in enum/const block declarations |
_ | Language / Convention | This is used to indicate a value that is not used for example in destructuring | |
_ in enum | Language / Convention | This is used to auto-increment the first value of that enum | |
Operators | Ternary | Language | Use if/else there is no ternary operator |
String Manipulation | uppercase | Language | strings.ToUpper("fOo") outputs FOO |
lowercase | Language | strings.ToLower("fOo") outputs foo | |
titlecase | Language | strings.ToTitle("fOo bAR") outputs Foo Bar | |
literals | Language | fmt.Sprintf("hello %s %s", name, surname) . Verbs | |
Gotchas | cant load package import cycle not allowed | Language | Import circularly reference each other see here |
Patterns | must | Convention | func 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 at1
instead of0
otherwise you can leave out the_
andSOUTH
would then be1
andNORTH
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 value0
.
- You need the
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 makeSOUTH
,EAST
andWEST
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 Student
s 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
}