vvek

Go 1.27 in practice: generic methods, goroutine leak profiles and a UUID package

A working developer's tour of the Go 1.27 release notes: generic methods, the goroutineleak profile, a standard uuid package, json v2 and go fix updates.

New Go releases tend to be quiet, and that is part of why people like the language. Go 1.27 keeps that character, but it does include a few changes you will notice in daily work. This is a tour of the ones that seem most useful, written from the official Go 1.27 release notes, with notes on what to check before you upgrade.

Generic methods

Until now, a Go type could have generic functions and generic types, but a method could not bring its own type parameters. You worked around it with a package-level function. In 1.27, according to the notes, "a method declaration may declare its own type parameters."

The standard library's own example is in math/rand/v2. There was already a generic function N that returns a random value of any integer type up to a limit. Now *Rand has an N method as well, so you can call it on a generator you own, instead of the shared global one.

Here is the shape of the feature in your own code:

type Store struct{ db *sql.DB }

// Get decodes a row into any type T the caller names.
func (s *Store) Get[T any](ctx context.Context, id string) (T, error) {
	var out T
	// ... query and decode into out
	return out, nil
}

user, err := store.Get[User](ctx, "u_123")

Two limits are worth remembering. Methods declared on an interface still cannot have type parameters, and a generic method cannot be used to satisfy an interface method. So this is a convenience for concrete types, not a new way to build abstractions. If you were about to reach for it to model an "interface with generic methods", the notes say plainly that it will not work.

Two smaller language changes arrive alongside it. A struct literal key can now be any valid field selector, not only a top-level field name. And type inference for generic functions has been widened to every place where a generic function is assigned to, or converted to, a matching function type. Both remove small bits of ceremony.

A goroutine leak profile you can turn on

Leaked goroutines are one of the classic ways a Go service slowly gets heavier. Memory climbs, nothing crashes, and a restart hides it for a while.

Go 1.26 had a goroutineleak profile as an experiment. In 1.27 it is generally available in runtime/pprof, and over HTTP at /debug/pprof/goroutineleak if you already expose net/http/pprof. The experiment flag is gone.

The notes define a leak as a goroutine blocked on a concurrency primitive, such as a channel, mutex or condition variable, that can never be unblocked. The detection leans on the garbage collector. If the primitive is unreachable from any goroutine that could run, nobody can ever wake the blocked one, so it is reported.

Note

The notes state a limit: leaks can be missed when the blocking primitive is reachable through a global variable or through local variables of goroutines that are still runnable. A clean profile is good news, not a proof.

A typical leak the profile can catch looks like this:

func fetch(ctx context.Context, url string) (string, error) {
	ch := make(chan string) // unbuffered
	go func() { ch <- slowCall(url) }() // blocks forever if the caller left
	select {
	case v := <-ch:
		return v, nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

If the context is cancelled first, nobody reads from ch, and the goroutine waits forever. The fix is a buffered channel of size one. Having a profile that names such goroutines turns an afternoon of guessing into a short list.

A uuid package in the standard library

The release notes say "the new uuid package generates and parses UUIDs." That is all the page says about it, so check the package documentation for the exact functions and versions it supports. For many projects this means one dependency less. Teams that use UUIDs as database keys or as idempotency keys will likely want to look at it first. Do not swap the dependency in a hurry: confirm that it produces the identifier version your schema and sort order assume.

JSON, now on the v2 implementation

encoding/json is now backed by the v2 implementation, and the new encoding/json/v2 and jsontext packages are available directly. For most programs the old API behaves as before, but this is the change I would test most carefully, because JSON sits on every boundary. The release notes mention an opt-out, GOEXPERIMENT=nojsonv2, and say it is expected to go away in a future release. Treat it as a short bridge while you find any differences in your own payloads, not as a setting to keep.

Smaller items that affect your workflow

  • go fix gains modernizers named atomictypes, embedlit, slicesbackward and unsafefuncs. The fmtappendf analyzer was removed, and waitgroup was renamed waitgroupgo. Run go fix on a clean branch and review the diff like any other change.
  • go mod tidy now merges duplicate require blocks for modules that declare go 1.27 or later, leaving at most one block for direct and one for indirect dependencies. Expect a one-time diff in go.mod.
  • go test runs the stdversion vet check by default, which flags use of standard library symbols newer than your module's Go version.
  • The old asynctimerchan setting is gone, so channels created by the time package are always unbuffered.
  • The notes say small allocations, under 80 bytes, can be up to 30% cheaper. Treat that as an upper bound and measure your own service.

A sensible upgrade path

  1. Read the notes top to bottom once. They are short.
  2. Bump the toolchain in a branch, build, and run your tests with the race detector.
  3. Run go vet and read any new stdversion or printf findings.
  4. If you handle a lot of JSON, replay recorded payloads through old and new builds and compare.
  5. Turn on the goroutine leak profile in staging and look at it after a load test.
  6. Only then change go.mod to go 1.27, so the tidy and vet changes arrive as a separate, reviewable commit.

None of this is dramatic, which is the point. Generic methods will likely tidy a few APIs, the leak profile gives you a new place to look when memory creeps, and the rest is housekeeping that is cheap to do on a calm afternoon.

Sources

Keep reading

A short email when we publish. Confirm once, unsubscribe any time.