Why?

Just for shits and giggles and procrastination, for the sole reason that this tutorial showed up in my Mastodon feed today. I'm also intrigued by the number of cool terminal apps I've been seeing made in Go, and maybe some other categories of things which I'm forgetting. Also, a) binaries and b) Kelsey Hightower. As it also turns out, the syntax is extremely simple.

First pleasant surprises: Succinctness

No semicolons! No parentheses for conditionals! These two make it feel a bit like home for this Pythonista.

Private/public functions simply by having a lowercase/uppercase first letter, respectively!

Named return values! After the closing parenthesis of a function's parameters, (<name> <type>) instantiates <name> with the value 0 (or "" for a string, etc.), and you can assign to this variable in the body of the function. When you do return with nothing after it, the named return value will be returned. This feels nice to me:

func myFunc() (meaning int) {
    meaning = 42
    return
}

For lack of a better term, "bulk" type declarations. So if x and y are both meant to be integers, you can specify them as

func myFunc(x, y int)

I could see this being a tiny bit ambiguous, but it's another feature leading to succinctness.

Docstrings are just // comments above a function. I guess they're called "documentation" rather than docstrings (Python-speak):

// What I write here will automatically be scooped up into the documentation (which can be generated automatically with [godoc](https://pkg.go.dev/golang.org/x/tools/cmd/godoc).
func myFunc() int {
    return 0
}

Built-in Testing Framework

Simply name your test module with _test.go at the end, name your test functions with Test at their start, and finally add a boilerplate-y argument t *testing.T as the only argument to your tests. Oh, and import "testing":

// app_test.go

import "testing"

func TestMyFunc(t *testing.T) {
    return nil
}

Also similar to Python, you've got something akin to doctests built in: Prepend a function in a test module with Example which ends with `// Output: and this will a) generate some documentation and b) be tested! Without that comment, it will generate the docs but won't run the test (what test?).

...with Benchmarking!

This is another piece of the built-in test suite: Prepend a function in a test module with Benchmark, then add the -bench=. flag, and it'll run the function a bunch of times and tell you a) how many times it ran and b) how long each iteration took.

godoc! Your documentation will appear in a localhosted site alongside the stdlib and any packages you have installed.

...with a Coverage Tool!

$ go test -cover # 👊

Only One Kind of Loop

Spoiler: it's for. 😄

Incredible neovim Support

I did the laziest Google research for setting up neovim for golang development, installed vim-go (and did :CocInstall coc-go) and man, there is a lot of magic happening:

Having initialized a go module, if I create a test module it automatically populates it with a bunch of boilerplate:

package main

import "testing"

func TestHelloWorld(t *testing.T) {
    // t.Fatal("not implemented")
}

Also, auto imports, literally as you type (and removal of unused imports). Okay, it's actually on save.

Two Ways to Create a function

Similar to JavaScript, you've got both the expected function declaration:

func FuncName(params) {
}

as well as function assignment (sic?):

FuncName := func(params)

Arrays and Slices

Similar to C, Java, and other languages, you have to declare the length of an array in Go. However, when you define a function which accepts an array as a parameter, Go is a bit stricter than C: You actually have to specify the length of the array! However, slices are used far more than arrays in Go, which are declared like arrays but without giving a value for length:

mySlice := []int{1, 2, 3}

This allows for more flexibility in function definitions.

Sidenote: If you want the compiler to set the length of an array at declaration you must use ...:

myArray := [...]{1, 2, 3}

Like arrays, slices can't be resized, but there's a builtin for creating new slices from old ones while adding some values—

append(slice []Type, elems ...Type) []Type

—as well as creating an empty slice of a certain length:

make([]int, 10)

Then there's

copy(dst, src []Type) int

Slicing slices and arrays is pretty much exactly like in Python.

Two things which have to be considered when creating slices from large arrays is that if you don't use copy:

1: Any changes made to the slice will also be made to the array (the slice is just a reference to a portion of the array).

2: The memory used by the array will not be garbage-collected (of concern especially if it's a large array and the slice is small) so long as the slice is in scope/use.

I also discovered that if you allocate, say, three slots in a slice, then try to copy all elements of an array of length > 3 into it, there won't be an error: The first three elements of the array will be copied into the slice.

smiley picture of shaven-head Zev

American programmer living in Switzerland. Constant learner, chess improver, speech-to-text. 🌼