Go Tips

Here lies my collection of random things I've learned how to do in Go.

Nice Logs

I use slog for everything. In production, I use a custom Google Cloud Run handler, but in development, I like to output everything in pretty colors. charmbracelet/log to the rescue!

package main

import (
	"log/slog"
	"os"

	charmlog "github.com/charmbracelet/log"
)

func main() {
	isDev := flag.Bool("dev", false, "enable development mode")
	flag.Parse()

	var handler slog.Handler
	if *isDev {
	    handler = charmlog.New(os.Stderr)
	} else {
	    handler = slog.NewJSONHandler(os.Stdout)
	}
	
	slog.SetDefault(slog.New(handler))
	slog.Info("hello, world!")
	slog.Warn("ummm")
	slog.Error("oh no")
}

Shuffling a List of Items on Repeat

2026-02-28

Think about when you want to show a list of loading messages to a user. You don't want to show them in the same order, every time, and you don't want to show an item multiple times when the list hasn't been exhausted yet.

Here's how I do it.

package main

import (
	"fmt"
	"math/rand/v2"
	"time"
)

func main() {
	messages := []string{"Reticulating splines", "Cohorting Exemplars", "Deunionizing Bulldozers", "Resolving GUID Conflict"}

	var buffer []string
	var message string

	tick := time.Tick(time.Second)
	done := time.After(10 * time.Second)
	for {
		if len(messages) == 0 {
			messages, buffer = buffer, messages
		}

		rand.Shuffle(len(messages), func(i, j int) {
			messages[i], messages[j] = messages[j], messages[i]
		})

		message, messages = messages[0], messages[1:]
		buffer = append(buffer, message)

		select {
		case <-tick:
			fmt.Println(message)

		case <-done:
			fmt.Println("done!")
			return
		}
	}
}

Time Formats

Created 2025-04-28, Updated 2026-02-28

There are a lot of time formats that I find myself frequently using.

I get the impression these are not well-known, even though they're built into the standard library.

package main

import (
	"fmt"
	"time"
)

func Example() {
	formats := []string{
		time.DateOnly,
		time.TimeOnly,
		time.RFC3339,
		time.Kitchen,
	}

	now := time.Now()
	for _, format := range formats {
		fmt.Println(now.Format(format))
	}

	// Output:
	// 2009-11-10
	// 23:00:00
	// 2009-11-10T23:00:00Z
	// 11:00PM
}

Marking a Context as Done with a Signal

2025-07-12

The signal package has a useful function called NotifyContext that will notify the Done channel of the Context when one of the listed signals is received by the running process.

Watch out! When you use this, the exit method will no longer be called when an interrupt is received, so you'll need to call it yourself.

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
)

func main() {
	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)

	fmt.Println("press Ctrl-C to exit...")

	// waits
	<-ctx.Done()
	os.Exit(0)
}

Emulating Closed Sum-Type / Discriminated Unions using the AsAny Pattern

2025-05-04

OpenAI's Go library makes extensive use of this, and I quite like it.

The trick is to use a sealed-interface union wrapper. It's a struct with mutually-exclusive pointer fields plus an unexported marker interface. You don't use the wrapper directly; use constructors to instantiate new instances.

package main

import "fmt"

type Dog struct{}

func NewDog(d Dog) PetUnion {
	return PetUnion{OfDog: &d}
}

func (Dog) bark() { fmt.Println("bark") }

type Cat struct{}

func NewCat(c Cat) PetUnion {
	return PetUnion{OfCat: &c}
}

func (Cat) meow() { fmt.Println("meow") }

// keep this private
type anyPet interface {
	implAnyPetUnion()
}

func (Dog) implAnyPetUnion() {}
func (Cat) implAnyPetUnion() {}

type PetUnion struct {
	OfDog *Dog
	OfCat *Cat
}

func (u PetUnion) AsAny() anyPet {
	switch {
	case u.OfDog != nil:
		return u.OfDog
	case u.OfCat != nil:
		return u.OfCat
	}
	return nil
}

func Example() {
	pet := NewDog(Dog{})
	switch v := pet.AsAny().(type) {
	case *Dog:
		v.bark()
	case *Cat:
		v.meow()
	}
	// Output:
	// bark
}

Tickers / Recurring Tasks

Created 2025-04-28, Updated 2026-02-28

package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(time.Second)
	after := time.After(3 * time.Second)

	for {
		select {
		case <-tick:
			fmt.Println("ticked!")
		case <-after:
			fmt.Println("goodbye!")
			return
		}
	}
}

Capitalize Words

2025-04-28

package main

import (
	"fmt"

	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

func Capitalize(s string) string {
	return cases.Title(language.English).String(s)
}

func ExampleCapitalize() {
	fmt.Println(Capitalize("abcdef"))
	fmt.Println(Capitalize("hi there"))
	fmt.Println(Capitalize("CAPITALIZED"))

	// Output:
	// Abcdef
	// Hi There
	// Capitalized
}