- Background
- Go Tour
- Go Playground
- Slices
- Maps
- Struct Tags
- Packaging
- Method Receivers
- Variable Shadowing
- Globals
- Generics
- Testing
- Common Mistakes
- Style Guide
Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. Go is a statically-typed, compiled language. Go simplifies concurrent programming through goroutines managed by the Go runtime. The Go runtime is bundled in all Go binaries, which makes the starting size ~2Mb.
Go is an open source programming language.
The Go standard library is great. The following are just a few of the libraries provided:
- bytes - Manipulation of bytes
- context - Used to carry deadlines/signals between processes
- encoding - Encoding types (e.g.
JSON,XML) - crypto - Hashing and random numbers
- flag - Simplifies command-line flag parsing
- io - I/O primitives
- fmt - Standard I/O functions
- math - Math functions
- net - HTTP client/server implementations and related functions
- os - Platform independent OS function
- reflect - Runtime reflection
- regexp - Regex
- runtime - Access to the Go runtime
- sort - Sorting for primitives
- strconv - Conversions to/from
string - strings - String manipulation
- sync - Sychronization primitives and atomic operations
- testing - Unit testing and benchmarking
- time - Time
Check out the Go Tour. It provides an excellent introduction to Go.
The Go Playground makes it easy to share code snippits or quickly test some functionality. Note however, this environment uses the latest stable release of Go and time is also seeded.
Slices are dynamically-sized references to a backing array.
A nil value is valid for reads, but not writes.
Slices can be created using the built-in make function or through the short hand declaration:
len := 0
cap := 5
s1 := make([]int, cap) // [0 0 0 0 0]
s2 := make([]int, len, cap) // [|X X X X X], X: Unknown cell, |: End of slice view
s3 := []int{0, 0, 0} // [0 0 0]Slices can be updated by their index as well as through the built-in append function:
subslice := s1[1:] // [0 0 0 0]
s1[1] = 1 // s1: [0 1 0 0 0], subslice: [1 0 0 0]
s2 = append(s2, 0) // [0|X X X]
s3 = append(s3, 0) // [0 0 0 0]Maps are dynamically-sized references that associate keys to values without order.
A nil value is valid for reads, but not writes.
A key can be any comparable type, which omits:
- Slices
- Maps
- Functions
- Any type composed of the former types
Maps can be created using the built-in make function or through the short hand declaration:
cap := 1000 // Specify at least how large the initial map must be
m1 := make(map[string]string, cap) // {| X X ...}, X: Unknown cell, |: End of map view
m2 := map[string]string{
"learn": "Go",
// Other elements
}Maps are updated by their index and automatically resize to accomodate new indices:
m1["learn"] = "Go" // {"learn": "Go"}
m2["write"] = "documentation" // {"learn": "Go", "write": "documentation"}Struct tags are meta data associated with structs, which can be accessed at runtime using reflect. Tags are key-value pairs normally used to specify rules for encoding and decoding data. Valid tags are outlined for each data format (e.g. JSON).
Import paths should refer to packages stored on the local file system. To refer to external packages, they should be vendored. Circular dependencies are not alllowed.
Packages are imported according to their directory structure.
Packages can also be aliased (prevent packages collisions or shorten name) with . and _ as special cases:
import (
"net/http"
ht "net/http/httptest"
"github.com/google/go-cmp/cmp"
. "math" // Exported fields directly accessible (and potentially shadowed by current package) without a [package]. prefix
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // The package and any associated packages have init() side effects
)To publically export/expose a name outside of a package, it must be capitalized (e.g. fmt.Println).
Further, any exported member should have documentation.
Similar to interfaces, packages should be boundaries that well define their responsibilities. Try to avoid creating mono-packages so that only the required functionality is imported. Slim packages make easy to understand APIs that are hard to misuse. However, packages should not be broken up if they cannot function/exist without each other.
Checkout the standard project-layout repository. This can vary from project to project, but generally most of it should be applicable.
The following are nearly standard structure:
/cmdstores executables/teststores external tests (non-unit tests)/pkgthe top or nearly top level directory for source code
A value receiver type will be shallow copied when passed into a method. A pointer receiver will pass an address/reference/pointer type into a method.
Consider the following:
type PipelineRun struct {
metav1.TypeMeta
metav1.ObjectMeta
Spec PipelineRunSpec
Status PipelineRunStatus
}
func (p PipelineRun) doSomething() {
// Does something
}
func (p *PipelineRun) SetSpec(s PipelineRunSpec) {
p.Spec = s
}The doSomething method definition has a PipelineRun value receiver, which means the PipelineRun will be copied.
The PipelineRun struct has two fields (spec and status) and two embedded types (metav1.TypeMeta and metav1.ObjectMeta), which makes this a relatively expensive operation.
Method receivers are passed alongside any parameters, which themselves follow these same copying rules.
For example, the SetSpec method definition (pointer receiver) has a PipelineRunSpec parameter that is passed by value (copied).
Using a value receiver can be a good fit when:
- The method is not modifying state
- The value is relatively small (avoids pointer indirection)
Use the same receiver type for all methods if possible.
Variable names shadow values of the same name within parent scope(s).
This makes the previous values inaccessible:
package main
import (
"fmt"
)
var x int
func main() {
fmt.Println(x) // 0
x := 1
fmt.Println(x) // 1
{
x := 2
fmt.Println(x) // 2
}
fmt.Println(x) // 1
fmt := "string"
fmt.Println(fmt) //fmt.Println undefined (type string has no field or method Println)
}There are a lot of opinions on global state. Consider alternatives if possible when creating globally mutable variables, especially for exported values.
Go does not have generics, but there is a draft proposal. To achieve the same effect, it is a common practice to use code generators such as kubernetes/code-generator or gen.
Go has great testing built into the standard library.
The testing package provides support for unit testing and benchmarking.
It is idiomatic to write table driven tests. When comparing for equality, favor github.com/google/go-cmp/cmp over reflect.
Check out these common mistakes.
- Verbose: Effective Go
- TLDR: Code Review Comments