Skip to content


Created: 2020-06-11 08:51:39 -0700 Modified: 2022-01-30 10:26:22 -0800

  • Resources
  • Selecting a specific version
  • Properties of the language
    • Great talk by Rob Pike, one of the creators of the language, on the simplicity of the language
      • There’s a garbage collector, but it isn’t exposed to the programmer
      • Go is fast because it’s compiled (so there’s no REPL).
      • Interfaces are just methods; there won’t be any extra data with them.
      • Rarely will you find multiple ways to program the same thing.
  • Go is camelCased (although PascalCase is required for exporting names).
  • Game engines/rendering:
    • From my relatively basic research, these seem like the best options if you’re going to do anything with game development:
      • SDL bindings: GitHub, example code. This isn’t exactly a game engine, but it has input-handling, rendering, audio, text rendering, and some other features.
      • raylib-go: GitHub: input handling, sound, shaders, physics.
  • Terminology
    • A package is a collection of source files in the same directory that get compiled together
    • A module is a collection of packages that are distributed together.
    • An import path is a string used to import a package.
  • Modules
    • Modules are relatively new (2018-2019) (reference).
    • There is no centralized repo for Go modules like there is for Node with NPM.
    • Getting started: the first thing you’ll do for most Golang projects is something like “go mod init yourmodulename”. This will create a “go.mod” file which will you install dependencies. After that, you can paste in a “hello world”-style program, do “go run main.go” and it’ll automatically install what’s imported.
    • When you use “go install”, “go build”, or “go run”, it will automatically download the remote module into $GOPATH/pkg/mod. These downloaded modules are versioned and will be reused by all modules that require the same version. This saves a lot of disk space!
      • “go get” vs. “go install”: see this StackOverflow post
      • ”go get” is like “npm install”, but it doesn’t go into a local “node_modules” equivalent, which is fine due to how Go tries to reuse versioned packages across multiple folders.
        • ”go mod” will install them into a “vendor/” folder (e.g. “go mod init”). Doing so will also create a “go.mod” file to keep track of the dependencies just like package.json would for Node.
    • By running “go install <module path>”, you will get a binary in GOBIN,orifthatsnotset,inGOBIN, or if that's not set, in GOPATH/bin.
      • The Go programmers in chat said that they don’t actually use “go install” or an IDE handles it for them. Instead, people use “go build” or “go run” when developing a single module. “go get” and “go build” will both also get dependencies.
  • Testing
    • Make a file ending in “_test.go” (note that it does not need to match the name of an existing file), then prefix all functions with “Test”, e.g. “TestReverseString”. They need to take in a “t *testing.T”, which is essentially the test framework and gives you functions like t.Error and t.Fail.
    • Using the command “go test ./…” will test everything underneath the current directory.
  • Packages
    • Capitalized names are automatically exported from a package.
    • Packages start running the code in “package main”.
    • Every package can define its own “init” function (no args, no return value) that will get called after the package is imported (reference). This is as opposed to the main function which is only called for the executable’s entrypoint (reference).
    • The convention is that the package name matches the last element of the import path.
  • Return values
    • You can specify multiple return values by separating them with a comma, e.g. “return num1, num2”. The function signature then needs to look like “(int, int)” with parentheses.
    • You can name return values and then use a “naked” return to automatically return them (reference). This is only recommended for short functions apparently.
  • ”if” statement (reference)
    • Parentheses are optional, but curly braces are required.
    • You can have a statement before the condition of the “if” that will only be in-scope for the “if” statement itself (and its body and “else” statement) (reference). Here’s a common example of why you may want this:
if err := readFile(); err != nil { return err }
↑ statement ↑ condition

“err” won’t be in scope after the closing curly brace, meaning nothing else can use it, and it can be redefined.

  • Deferring function execution (reference)
    • The “defer” keyword lets you defer execution of a function until the surrounding function returns. The arguments to the deferred function will still be evaluated before its execution. This is typically used to clean up state, e.g. acquiring locks, closing connections, etc. This is especially helpful if a function has multiple “return” statements in it.
    • Deferred functions execute in inverse order (reference):
defer fmt.Println("this prints last")
defer fmt.Println("this prints first")
  • “switch” statements (reference)
    • No “break” is needed. It’s assumed by default, and if you want to fall through to other cases, you explicitly type “fallthrough”.
    • The values you’re comparing against don’t need to be constants and don’t need to be integers.
    • The individual cases stop executing when a match is found (reference). This means that if you have “case functionWithSideEffects()” after a match, then that function will never be hit.
    • Switch can be used without a condition at all, in which case it’s assumed to be “switch true”. At that point, it’s a cleaner-looking if-else chain and is apparently frequently used in some scenarios (reference).
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
fmt.Println("Good evening.")
  • “for” loop (reference)
    • There’s only one construct for this, and it’s in the form “for init; cond; post { body }” (don’t use parentheses, curly braces are required). Each part is optional, so an infinite loop could be expressed as “for ;; { body }“. This is how you would accomplish a “while” loop, although you’d even omit the semicolons (reference).
  • Garbage collection
    • Escape analysis (reference): this is a function of the garbage collector where it can decide whether a variable should be allocated on the heap or the stack (and remember that heap-allocated variables later need to be garbage-collected).
  • Types
    • Basic types are bool, string, byte, rune, and a bunch of numbers like int32, float64, complex128 (reference).
      • A rune is an alias for int32 (reference). Saying something like “const a = ‘a’;” (notice the single quotes) will make a rune whose value is 97. When you do something like reflect.TypeOf(someRune), you’ll see “int32”.
      • They suggest using “int” whenever you need an integer value unless you have a specific reason for a size or for an unsigned value (reference)
      • If you don’t initialize a primitive, it will get its “zero” value (0 for numeric types, false for bool, and an empty string for strings).
    • Type conversions can be done via TYPE(value), where “type” matches the primitive’s name exactly, e.g. “float64(5)” (reference)
    • For consecutive named function parameters or variable declarations of the same type, you only have to specify the type on the final param/var (reference):
// Example 1: named function parameters
func add(x, y int) int {
return x + y
// Example 2: variable declarations
var isWalkable, isObstruction, isVisible bool
  • Types can be inferred at initialization time (shown below is the initialization of two separate variables on a single line):
var favoriteNumber, favoriteColor = 5, "red"
  • You can use ”:=” inside of a function only for implicitly typed variables, e.g.
// Good
func main() {
k := 3
// use 'k' here somehow
k := 3 // Bad because it's not in a function
func main() {
j int := 3 // Bad because you can't explicitly type using colon-equals
const l := 5 // Bad because it's const, not a variable
  • You can alias your own types with the “type” keyword (reference): “type MyFloat float64”
  • Empty interfaces are like the “any” type in TypeScript in that they’ll accept any type. They look like “interface{}” (i.e. literally the word “interface” with curly braces after it). This is how functions like fmt.Printf accept arguments of any type (and how you’d write your own pretty-print library, for example).
    • You can get at the underlying type with “.(type)” (reference)
    • For maps, you’ll need to use reflection (reference) to get at individual key/value types, typically with reflect.TypeOf. For example:
func reflectExample(a interface{}) {
fmt.Printf("Type of 'a' is %Tn", a)
fmt.Printf("reflect.TypeOf: %vn", reflect.TypeOf(a))
fmt.Printf("reflect.TypeOf with Key: %vn", reflect.TypeOf(a).Key())
fmt.Printf("reflect.TypeOf with Elem: %vn", reflect.TypeOf(a).Elem())
func main() {
stringToIntMap := make(map[string]int64)
emptyInterfaceMap := make(map[interface{}]interface{})


Type of 'a' is map[string]int64
reflect.TypeOf: map[string]int64
reflect.TypeOf with Key: string
reflect.TypeOf with Elem: int64
Type of 'a' is map[interface {}]interface {}
reflect.TypeOf: map[interface {}]interface {}
reflect.TypeOf with Key: interface {}
reflect.TypeOf with Elem: interface {}
  • To drill into this more, you would do something like this to programmatically figure out whether you even have a map:
func test(value interface{}) {
reflectValue := reflect.Indirect(reflect.ValueOf(value))
switch reflectValue.Kind() {
case reflect.Slice, reflect.Array:
case reflect.Struct:
case reflect.Map:
  • reflect.Indirect is probably easiest to understand by looking at its simple source code.

  • Go V2 will apparently have generics (reference).

  • You can do type assertions to test or ensure that a type matches a particular interface (reference). The syntax is something like this:

// Test if "i" is a float64
f, ok := i.(float64)
fmt.Println(f, ok)
// Assert that "i" is a float64 and panic if it isn't
f = i.(float64)
  • You can use a type switch to allow for several different types. This uses empty interfaces and takes the form:
func do(i interface{}) {
switch v := i.(type) {
case int:
// now you know you have an int
case string:
// now you know you have a string
// you don't know which type you have (besides that it's not an int or string)
  • Pointers (reference)
    • Syntax is with * and & just like in C except that there’s no pointer arithmetic (reference).
var p *int // define a pointer to an integer
i := 42
p = &i // set p to point to the memory address of i
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p
  • A pointer’s default value is nil.
  • Pointers are essentially used the same way they are in C—you may want to pass something by reference instead of by value. However, maps and slices are automatically passed by reference without even needing an explicit pointer (reference), so this works:
func testModifyMap(m map[string]int) {
m["hello"] = 5
func main() {
myMap := make(map[string]int)
fmt.Printf("%vn", myMap) // prints "map[hello:5]"
  • In fact, passing a map by a pointer results in a value being

  • To access a member of a struct through a pointer, you would typically have to write ”(*p).X”, but that’s so common (just like in C++ with the arrow (”->”)) that Go allows you to just type “p.X” (reference).

  • Structs (reference)

    • Syntax example
// Definition
type Vertex struct {
X int
Y int
func main() {
// Creation
v := Vertex{1, 2}
// Access (set / get)
v.X = 4
  • You can name parameters when initializing (reference):
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
  • Why use pointers in structs? Good reasons listed here.

  • Arrays (reference)

    • Sample definition: var a [10]int
    • Arrays cannot be resized.
    • An uninitialized array will get all of the “zero” values of the elements, e.g. “var s [3]bool” will be [false false false].
    • Array literals (reference) - use these to build an array at the same time that you say its size, e.g.:

[3]bool{true, true, false}

  • Slices (reference)
    • Slices are dynamically-sized, flexible views into the elements of an array. Sample definition:
var s []int = primes[1:4] // this represents elements 1 to 3 (exclusive upperbound)
  • A slice does not actually store any data. It’s like a reference to the array (reference). Modifying the elements of a slice will also modify the underlying array and any other slices that reference those elements.
  • Slice literals - very similar to array literals in that it creates an array, but then you get a slice that references the whole array:
[]bool{true, true, false}
  • You can omit either of the bounds for a slice (reference) (just like you can in Python)
  • Slices have a length and a capacity (reference). The length refers to the size of the slice itself (i.e. how many elements it contains), whereas the capacity is the size of the underlying array.
  • An uninitialized slice will be nil (reference)
  • You can use the make function to create dynamically-sized arrays (reference). “make” has three parameters (reference): the array type, the length, and the optional capacity.
a := make([]int, 5)
  • Slices can contain any type, including other slices (reference). This is like having an array of arrays.
  • You can append to a slice using the “append” function (reference). This will not change the slice that you pass in; instead, you have to use the return value of “append”.
m := append(s, 2, 3, 4)
  • Range (reference)
    • You can use a “for” loop to iterate over a slice:
for i, v := range an_array_of_numbers {
fmt.Printf("2**%d = %dn", i, v)
  • If you only want the index, just omit the second variable (“v” above).

  • If you only want the value, specify ”_” instead of an alphanumeric name.

  • Map (reference)

    • Sample definition: var m map[string]Vertex
      • The key type is “string” and the value type is “Vertex”
    • Map literals (reference):
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
"Google": Vertex{
37.42202, -122.08408,

Note that you could have omitted “Vertex” in the initialization (i.e. you remove 2 of the 3 instances of “Vertex”) because the type is known.

  • Examples of making empty maps rather than nil maps:
// This works, but you should probably use "make"
wordCount := map[string]int{}
// This is probably preferred
wordCount := make(map[string]int)
  • CRUD operations on maps: see this link

  • Maps are key/value pairs. They start as nil and, like slices, can be made via “make”.

  • Functions (reference)

    • Functions are first-class, meaning they can be used as values, e.g. being passed to other functions.
    • Anonymous functions can be declared like this (reference)
return func(x int) int {
sum += x
return sum
  • Closures (reference) - functions can refer to variables outside of their defining scope.
  • There is no concept of function overloading. Go’s internals accomplish something like this apparently with type assertions (reference); you’ll see code like this:
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic

The first one doesn’t cause a panic because Go sees that you’re using both return values of the type assertion. However, if you wanted to get this logic from your own functions, apparently you wouldn’t be able to.

  • Methods (reference)
    • Methods are functions with a special receiver argument. They’re essentially Go’s concept of classes. Syntax (reference):
func (receiver Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
  • Notes
    • Vertex is just a struct. You can make methods on any type as long as it’s defined in the same package, meaning you’ll have to alias built-in types like “float” using the “type” keyword if you want a float method.
    • To call this Abs function, you would do something like this
v := Vertex{3, 4}
  • You don’t have to capitalize method names, but you probably want them to be exported.

  • This is pretty much exactly the same as just writing Abs to take in a Vertex rather than using the special receiver argument.

  • It’s more common to see pointer receivers (i.e. receivers that are pointers to a type) than value receivers since the pointer would allow modification of the “class instance” (these aren’t actually classes, but that’s the behavior this is similar to).

    • When you have a pointer receiver and call it with something like v.Abs(), Go will automatically translate it for you into (&v).Abs() (reference). Likewise, when you have a value receiver and call it with a pointer, it will turn it into (*v).Abs() (reference).
    • Pointer receivers are necessary if you want to modify the value that the receiver points to. However, they’re also good for avoiding unnecessary copying since the arguments are passed by reference. Whatever you choose (pointer vs. value), don’t mix the two for methods on a particular type (reference).
  • You can call methods with nil values and not get something like a NullPointerException. E.g.

type Person struct {
Name string
func (person *Person) sayName() {
if person == nil {
fmt.Println("<no name>")
func main() {
// This is a pointer that we never initialized, so its "zero" value is nil
var person *Person
// This is essentially nil.sayName(), but we know the type of "person" is "Person", so we still call the correct function.
  • Interfaces (reference)
    • An interface is a set of method signatures.
    • Golang uses structural typing (like TypeScript) (reference). This means that a type essentially just has to satisfy the right shape as opposed to matching in name (like in “nominative”/“nominal” systems which are based on the name, e.g. a function that expects a width and height wouldn’t allow a Rectangle in a nominative system because it doesn’t match exactly in name).
      • As a result, there is no “implements” keyword in Go; if your type matches the interface’s signature, then it implicitly implements the interface.
      • Here’s an example from the tour. This example is a little bit difficult to understand if you’re new to Go since you’re fighting against syntax and their unhelpful one-letter names:
func (t T) M() {

This is a method named “M” that has a receiver named “t” of type “T”, which they declare is a struct. They use this to show that “T” implements their interface since the interface only requires the type to have an “M” function.

  • Interface values (reference) have a concrete type since there’s no explicit implementation of an interface. Just like with any variable, there’s a value and a type, but you’ll never see the type match the name of an interface.
    • Because there’s a concrete type, even when the underlying value is nil, we’ll still know which methods to call (reference).
      • This is different from a nil interface value (reference), which represents this:
type I interface {
func main() {
// We never gave "i" a concrete type, so it's a nil interface value
var i I

Calling any function/method “” will be a runtime error since there is no concrete type.

  • I wrote notes about empty interfaces (i.e. “interface{}”) under “Types”.

  • To get something like toString() in other languages, you’ll implement the Stringer interface (reference). Thanks to structural typing, this just requires that you make a “String” method that returns a string.

  • Readers (reference) - common interfaces are io.Reader and io.Writer. Find out more at the reference link.

    • It’s common for a Reader to wrap another Reader to modify the stream in some way (reference).
  • Errors (reference)

    • Just like with the fmt.Stringer interface, there’s a built-in “error” interface that has a single method “Error()” that returns a string. This link from the tour has an example of defining your own error and returning it from a function.
    • The convention is to always return the error last from a function that may error. Thus, as a caller of such a function, you’ll typically do something like “returnValueYouWant, err := someFunctionCallThatMayError()” and then check “err” against nil.
    • Making a new error: just use errors.New(“some message here”) for an unformatted error or fmt.Errorf(“Wanted %d apples”, 5) for a formatted one. Some style guides recommend always using fmt.Errorf so that you don’t need to change constructions based on whether or not you have formatting arguments.
    • Go’s errors vs. exceptions (reference): there’s a whole conversation online about Go’s errors vs. checked exceptions (like Java). The reference link has plenty of information; what I added below is just to clarify.
      • Checked vs. unchecked exceptions: first of all, the “checked” part refers to the fact that the exception is checked by the compiler (i.e. the method must annotate that it throws the exception and the caller must do something about catching it). Java also has unchecked exceptions (reference), which are exceptions that explicitly inherit from RuntimeException or Error; these exceptions are not checked by the compiler, so any programmer can write code without bothering to specify or catch these exceptions. One common unchecked exception is a NullPointerException, meaning you can call a function defined like “public void greet(String message)” like “greet(null)” and your program will compile just fine.
      • Comparison between the languages: Go only forces you to follow a function’s contract. E.g. if a function returns an error, then callers will need to store that return value (even if it’s just in the blank identifier (the underscore)). In that sense, it’s similar to Java since you could always catch an exception and just do nothing about it. However, if f() calls g() and g() calls h(), then f() doesn’t need to handle h()‘s potential errors (like it might in Java). Also, there’s no stack unwinding, so there’s no performance overhead for the runtime. Go doesn’t have a need for another set of control structures in the form of try/catch/finally.
  • Goroutines (reference)

    • Goroutines are lightweight threads managed by the Go runtime. Sample syntax: go f(x, y, z) - this starts a new thread running the function specified (“f” in this case).
      • The evaluations of each individual component (“f”, “x”, “y”, and “z”) are all done in the current thread, but the function execution itself will take place in the new thread.
      • All threads will share the same address space, so you’ll need to synchronize access (reference).
    • sync.Mutex (reference)
      • You can ensure mutual exclusion with calls to “Lock” and “Unlock” (reference). You can ensure that the “Unlock” call happens by deferring it (reference).
  • Channels (reference)

    • You can send and receive values through channels. By default, both operations block until the other side is ready (see “select statements” below for information on non-blocking communication). Sample syntax
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
// Create a channel
ch := make(chan int)
  • Buffered channels will block based on a buffer size that you pass to “make” (sends block when full, receives block when empty).

  • Range and close (reference) - a sender can close a channel to indicate that no more data should be sent over it. By using “range CHANNEL”, where CHANNEL is a specific channel, you will receive values from the channel until it’s closed.

  • Select statements (reference) - select lets you wait on multiple communication operations. It blocks until any of the given channels have data on them. If multiple have data, it chooses one at random. This is sort of like “Promise.race” in JavaScript. The default case is run if no other case is ready (reference).

    • Select + default (reference) - if your “select” has a “default” case and none of the non-default cases are matched, then communication will not block.
  • Sets

    • Sets don’t exist in Go, so if you want to test inclusion, use a map of bools:
foods := map[string]bool{
"apple": true,
"banana": true,
"carrot": true,
if foods["apple"] {
fmt.Printf("FOUND YOUR FOOD")

This is the code I ended up with for the various exercises in the documentation. I don’t know if any of it is good since I didn’t know Go before doing this.

func Sqrt(x float64) float64 {
z := 1.0
lastValue := 0.0
for i := 0; i < 10; i++ {
if z == lastValue {
fmt.Printf("Num iterations to find value: %vn", i)
return z
lastValue = z
z -= (z*z - x) / (2*z)
return z
package main
import ""
func Pic(dx, dy int) [][]uint8 {
board := make([][]uint8, dy)
for y := 0; y < dy; y++ {
row := make([]uint8, dx)
for x := 0; x < dx; x++ {
var pixel uint8 = uint8((x ^ y))
row[x] = pixel
board[y] = row
return board
func main() {
package main
import (
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e));
func Sqrt(x float64) (float64, error) {
if x < 0 {
return 0, ErrNegativeSqrt(x)
z := 1.0
lastValue := 0.0
for i := 0; i < 10; i++ {
if z == lastValue {
fmt.Printf("Num iterations to find value: %vn", i)
return z, nil
lastValue = z
z -= (z*z - x) / (2*z)
return z, nil
func main() {
value, err := Sqrt(-2)
if err != nil {
fmt.Println("Got an error:", err)
} else {
fmt.Println("Got the value:", value)
func (reader MyReader) Read(bytes []byte) (int, error) {
for i := 0; i < len(bytes); i++ {
bytes[i] = 'A'
return len(bytes), nil

(I cut a corner by only doing rot13 for lowercase ASCII letters)

package main
import (
type rot13Reader struct {
r io.Reader
func (reader rot13Reader) Read(bytes []byte) (int, error) {
b := make([]byte, 8)
n, err := reader.r.Read(b)
if err != nil {
return n, err
for i := 0; i < n; i++ {
b[i] -= 'a'
b[i] += 13
b[i] = b[i] % 26
bytes[i] = b[i] + 'a'
return len(bytes), nil
func main() {
s := strings.NewReader("lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
package main
import (
type Image struct{
w int
h int
func (im Image) ColorModel() color.Model {
return color.RGBAModel
func (im Image) Bounds() image.Rectangle {
return image.Rect(0, 0, im.w, im.h)
func (im Image) At(x, y int) color.Color {
v := uint8((x * y))
return color.RGBA{v, v, 255, 255}
func main() {
m := Image{250, 250}

Note: this code doesn’t need to sort and doesn’t actually solve the problem, but it has the foundation for it.

package main
import (
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
walkImpl(t, ch)
func walkImpl(t *tree.Tree, ch chan int) {
if t == nil {
ch <- t.Value
walkImpl(t.Left, ch)
walkImpl(t.Right, ch)
type ByNumber []int
func (a ByNumber) Len() int { return len(a) }
func (a ByNumber) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByNumber) Less(i, j int) bool { return a[i] < a[j] }
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
v1 := make([]int, 1)
v2 := make([]int, 1)
for i := range ch1 {
v1 = append(v1, i)
for i := range ch2 {
v2 = append(v2, i)
return true
func main() {
t1 := tree.New(10)
//ch := make(chan int)
//go Walk(t1, ch)
//for i := range ch {
t2 := tree.New(10)
areSame := Same(t1, t2)

Can’t build on Windows - “cc1.exe: sorry, unimplemented: 64-bit mode not compiled in”

Section titled Can’t build on Windows - “cc1.exe: sorry, unimplemented: 64-bit mode not compiled in”

You need mingw-w64, which for some reason was annoyingly difficult to figure out how to install (at least an hour), so that’s why I write these troubleshooting steps:

  • Download mingw-w64-install.exe from here
  • Run it
    • Version: choose latest
    • Architecture: x86_64
    • Threads: posix
    • Exception: seh
    • Build revision: 0
  • Add “C:Program Filesmingw-w64x86_64-8.1.0-posix-seh-rt_v6-rev0mingw64bin” to your PATH

(I know those steps are easy and relatively fast, but finding them took forever)