← Go, End to End

Go, End to End

A Tour of Modern Go

Go moves deliberately, this isn't a language that reinvents itself every release, but the last few years added real, substantial capability. Here's what's actually new and worth knowing, versus what's still settling.

Generics (1.18)

Already covered in depth in this course's dedicated Generics chapter. The short version: type parameters, constraints, and a handful of new standard library packages (slices, maps, cmp) built on top of them. This is the single biggest language-level addition in Go's history and it's fully stable, widely adopted in the ecosystem by now.

The evolving context package

context.Context started as "a way to propagate cancellation and deadlines through a call chain" and has become the de facto standard for that across the entire ecosystem, not just concurrent code. Recent additions include context.WithTimeout/context.WithDeadline conveniences and context.Cause, which lets you retrieve WHY a context was canceled (not just that it was), useful when several different timeout/cancel sources might be in play and you need to distinguish them for logging or retry logic.

slices, maps, and cmp (1.21)

Generic helper packages for the two container types you use constantly: sorting, searching, comparing, and reversing slices without hand-writing the loop every time; extracting/comparing map keys and values. cmp.Compare and cmp.Ordered give you a generic "less than" you can pass to sort functions without writing a custom comparator for every type.

go · live, editable, runnable

Range-over-func iterators (1.23)

As of 1.23, range can iterate over a function with a specific signature (an "iterator function"), not just slices/maps/channels/integers. This lets library authors expose custom iteration (walking a tree, streaming results from a database cursor, lazily generating a sequence) using the same for ... range syntax callers already know, instead of a bespoke Next()/HasNext() pattern per library.

go
func Count(start, end int) func(func(int) bool) {
	return func(yield func(int) bool) {
		for i := start; i <= end; i++ {
			if !yield(i) {
				return
			}
		}
	}
}

// usage:
// for n := range Count(1, 5) {
//     fmt.Println(n)
// }

This is newer and the ecosystem is still working out idiomatic conventions around it (should a package expose an iterator alongside a slice-returning function, or instead of one?), worth knowing it exists, less worth building deep muscle memory around yet if you're just getting comfortable with the language.

The theme across all of these: Go adds capability slowly and conservatively, and almost always by extending the standard library rather than the language grammar itself. Generics were the one true grammar-level exception in over a decade.