We’ll finish the rest of the Tour of Go this time, which was started in the [previous post]({% post_url 2022-08-09-learn-go-i %}).

Methoding Around

The Tour’s next lesson is called Methods. But wait…there’s no classes, what are you calling methods on?

Types! A method is a specific type of function that can be identified by the way they are declared. A receiver argument appears after the keyword func and before the method name:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}
//Abs method has a receiver of type Vertex named v
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

Methods also can:

  • Be defined on primitive types like type MyFloat float64
  • Receive pointers, allowing them to act on the original entity, or be more efficient if the receiver is e.g. a large struct

Next up is Interfaces, defined as a collection of method signatures. This immediately sticks out as Go’s approach to Abstract types, a powerful OOP concept. You specify the functions you want children to implement before they can call your interface a parent:

package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v

	// This results in an error b/c Vertex (the type) doesn't
	// implement Abser b/c Abs() is not defined for Vertex,
	// only *Vertex
	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

The next several notes get into specific behavior about interfaces and type assertions that I skim through. I’ll come back to them when I need these language constructs.

They do mention the ubiquitous error interface. It’s very idiomatic to call a function, get return values, and handle errors by seeing if error is nil or not:

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

Finally there are several lesson at the end, trying to show off the Reader and Image interfaces from the standard library. I can only assume this Tour was aimed at REAL PROGRAMMERZ and not scrubs like me because I had no idea what the related exercises were asking and had no interest in figuring it out. I figured the Tour was a gentle tutorial for beginners but it seems to assume prior programming experience. Whatever…

General Generic

I remember seeing on the interwebs all the Go-hate because it didn’t support “generics” and the subsequent rejoicing when it finally did. As someone who spent a lot of time with Python, I understand the power of not caring about what type you’re working with, you just wanna do the thing with it. I’m glad I got into Go when I did.

Functions and Types can be generic. Special syntax and keywords are used to indicate genericity:

package main

import "fmt"

// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
	for i, v := range s {
		// v and x are type T, which has the comparable
		// constraint, so we can use == here.
		if v == x {
			return i
		}
	}
	return -1
}

// List represents a singly-linked list that holds
// values of any type.
type List[T any] struct {
	next *List[T]
	val  T
}

func main() {
	// Index works on a slice of ints
	si := []int{10, 20, 15, -10}
	fmt.Println(Index(si, 15))

	// Index also works on a slice of strings
	ss := []string{"foo", "bar", "baz"}
	fmt.Println(Index(ss, "hello"))
}

Con Concurrency

This is where Go stands out. Goroutines are a lightweight thread managed by the Go runtime and there is the special go keyword for running them:

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(1000 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}
$ run main.go
hello
world
world
hello
hello
world
world
hello
hello

Channels are a special conduit to send and receive values. Like maps and slices, they must be created before they can be used. make() does that too. Reading and writing values to a channel has it’s own operator <-. These have properties that allow goroutines to synchronize without locking. This is another language feature I need to use before understanding fully:

package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}
$ run main.go
-5 17 12

Channels can be closed with the close() function. No more values can be sent to a closed channel. It’s idiomatic to use range to iterate over channel values until it’s closed.

There’s a select keyword that allows a goroutine to wait for some condition before operating. It uses the case keyword and looks like a switch statement:

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}
$ run main.go
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
BOOM!

Next Steps

And that’s essentially the end of the Tour of Go! Overall, Go looks to be an interesting, modern language with some cool constructs built-in. And I haven’t even seen the stuff that brought me to it: the build/deploy/management of Go binaries. I’m slightly disheartened by my struggles at certain points in the tutorial but more practice with the language should fix that.

Armed with a thorough introduction to language syntax and design, actually writing code is the next move.