--- name: go-127 description: > What changed in Go 1.27 (released August 2026) and how it changes the way Go is written in pi-go. Use this skill when writing or reviewing Go that could use a 1.27 feature, when bumping the `go` directive in go.mod, when a build or test behaves differently after a toolchain upgrade, or when `code-guidelines-go` points here. Covers generic methods, struct-literal field selectors, generalized type inference, the go command and go fix changes, the size-specialized allocator, the `goroutineleak` profile, `encoding/json/v2`, the new `uuid` and `crypto/mldsa` packages, `strings.CutLast`, `maphash.Hasher`, `synctest.Sleep`, `httptest.NewTestServer`, and every removal that can break a build. --- # Go 1.27 — What Changed and What To Do About It **Released**: August 2026, six months after Go 1.26. Go 1 compatibility holds; almost all programs compile and run unchanged. **Sources**: [go.dev/doc/go1.27](https://go.dev/doc/go1.27) (authoritative) · [victoriametrics.com/blog/go-1-27](https://victoriametrics.com/blog/go-1-27/) (worked examples). Every claim below is from one of those two, or from reading `$(go env GOROOT)/src` on the installed 1.27.0 toolchain. Companion skill: `code-guidelines-go` holds the standing style rules. This skill holds only what 1.27 *changed*. --- ## 0. Read this first — `GOEXPERIMENT=none` is set on this machine `go env` on this host persists `GOEXPERIMENT=none`, which does **not** mean "no extra experiments" — it clears the 1.27 *baseline*, which enables `JSONv2`, `GreenTeaGC`, `SizeSpecializedMalloc`, `Dwarf5` and `RandomizedHeapBase64` (`src/internal/buildcfg/exp.go:81`). Verified consequences here: ``` $ go build ./... # importing encoding/json/v2 imports encoding/json/v2: build constraints exclude all Go files in .../encoding/json/v2 $ golangci-lint --version golangci-lint has version 2.13.1 built with go1.27.0-X:nojsonv2,nogreenteagc, norandomizedheapbase64,nosizespecializedmalloc ``` So on this machine `encoding/json/v2` will not import, and the 1.27 allocator and GC work are switched off. Before benchmarking anything or trying a v2 snippet: ```bash go env GOEXPERIMENT # prints "none" -> baseline is cleared go env -u GOEXPERIMENT # restore the default baseline (user decision — ask first) GOEXPERIMENT=jsonv2 go build ./... # or scope it to one command ``` Do not silently change the user's `go env` config. Flag it, and scope the experiment to the command you are running. --- ## 1. Language changes ### 1.1 Generic methods — methods may declare their own type parameters The headline change. Before 1.27 a generic operation on a type had to be a package-level function; now it can live on the type. ```go // ✗ Before 1.27 — generic op stranded at package scope type Box[T any] struct{ v T } func MapBox[T, U any](b Box[T], f func(T) U) Box[U] { return Box[U]{v: f(b.v)} } // ✓ Go 1.27 — the method declares its own type parameter U type Box[T any] struct{ v T } func (b Box[T]) Map[U any](f func(T) U) Box[U] { return Box[U]{v: f(b.v)} } b := Box[int]{v: 21} label := b.Map(func(n int) int { return n * 2 }). Map(func(n int) string { return fmt.Sprintf("value=%d", n) }) ``` Two hard restrictions, both compiler-enforced: - **Interfaces may not declare type-parameterized methods.** `Map[U any](...)` inside an `interface` fails with `interface method must have no type parameters`. - **A generic method cannot implement an interface method.** So a generic method is never part of a type's interface satisfaction set. **Prescription for pi-go**: this is the right tool for transforms on a concrete generic container (a typed cache, a typed result set). It is the *wrong* tool anywhere the type is consumed through an interface — and pi-go's design rule is "define interfaces at the consumer" (`code-guidelines-go` §1). If a call site takes an interface, keep the package-level generic function. ### 1.2 Struct literal keys may be any valid field selector A key in a struct literal is no longer restricted to a top-level field name, so promoted fields from an embedded struct can be set directly. ```go type Base struct{ ID int } type User struct { Base Name string } u := User{Base: Base{ID: 7}, Name: "Mittens"} // ✗ before 1.27, the only way u := User{ID: 7, Name: "Mittens"} // ✓ Go 1.27 ``` `go fix`'s new `embedlit` modernizer rewrites the old form for you (see §2.4). ### 1.3 Function type inference generalized Inference now applies in *every* context where a generic function is assigned to or converted to a matching function type — not just plain assignment. Composite literals and conversions now infer too. ```go func first[T any](s []T) T { return s[0] } func last[T any](s []T) T { return s[len(s)-1] } ops := []func([]int) int{first[int], last[int]} // ✗ before 1.27: explicit instantiation ops := []func([]int) int{first, last} // ✓ Go 1.27: element type drives T=int ``` The old failure was `cannot use generic function without instantiation` ([VM blog](https://victoriametrics.com/blog/go-1-27/)). If you see leftover `[int]` instantiations in a slice or map literal of function types, they can now be deleted. --- ## 2. Toolchain, `go` command, modules ### 2.1 `go test` runs `stdversion` by default `go test` now invokes the `stdversion` vet check, which reports use of stdlib symbols newer than the `go` directive in `go.mod` (adjusted by build tags on the file). This is the guard that catches "works on my machine": calling `strings.CutLast` from a module still declaring `go 1.26` is now a **test failure**, not a silent success. **Prescription**: when you use a 1.27 stdlib symbol, bump `go.mod` in the same change. Do not `//nolint` around it. ### 2.2 GODEBUG settings that were removed still parse From 1.27 the go command accepts a removed GODEBUG setting in `go.mod` (`godebug` lines) and `//go:debug` comments **if it is set to the final default value** established before removal. Set to an old value, the go command **fails**. So `//go:debug asynctimerchan=0` still builds; `asynctimerchan=1` is now a hard error. ### 2.3 `go mod tidy` enforces a two-block `require` layout For modules declaring `go 1.27` or later, `go mod tidy` merges duplicate `require` blocks into at most two — direct and indirect — preserving attached comment blocks. A comment block spanning a mixed set of directives is merged onto the direct block. **Expect a large `go.mod` diff** on the first `go mod tidy` after the version bump. Review it, do not fight it. ### 2.4 `go fix` modernizers Verified against `go tool fix help` on 1.27.0: | Analyzer | Rewrites | |---|---| | `atomictypes` | basic types in `sync/atomic` calls → atomic types | | `embedlit` | references to embedded fields in composite literals (see §1.2) | | `slicesbackward` | backward loops over slices → `slices.Backward` | | `unsafefuncs` | unsafe pointer arithmetic → function calls | | `waitgroupgo` | `wg.Add(1)`/`go`/`wg.Done()` → `wg.Go` — **renamed** from `waitgroup` | `fmtappendf` was **removed** (stylistic concerns). If a script or CI job names `waitgroup` or `fmtappendf`, it breaks. ### 2.5 `go doc` - `go doc example.com/pkg@v1.2.3` — documentation at a specific module version. - `go doc -ex ` — list a package's runnable examples (verified: `go doc -ex strings` interleaves `func ExampleTrimSpace()` lines under each symbol). - `go doc bytes.ExampleBuffer` — prints the example's source with comments. ### 2.6 Other tool changes - **`bzr` support removed** from the go command; modules on Bazaar servers can no longer be fetched directly. - **`go tool trace -http=:6060` now binds localhost only**, matching `go tool pprof`. Pass `-http=0.0.0.0:6060` to listen on all interfaces. Relevant to the `go-pprof` workflow in `AGENTS.md`. - **Response files (`@file`)** are accepted by `compile`, `link`, `asm`, `cgo`, `cover` and `pack`, in GCC's format. - **Linker**: `-macos` and `-macsdk` set OS/SDK versions in `LC_BUILD_VERSION`. Defaults: macOS 13.0.0, SDK 26.2.0. --- ## 3. Runtime, GC, performance, profiling ### 3.1 Faster small allocations — free, but off on this machine The compiler emits calls to size-specialized allocation routines, cutting the cost of some allocations under 80 bytes by **up to 30%**, ~1% overall in allocation-heavy programs, at ~60 KB more binary size (workload-independent). Nothing to change in code. Opt-out is `GOEXPERIMENT=nosizespecializedmalloc`, **expected to be removed in Go 1.28**. Per §0, `GOEXPERIMENT=none` on this host already disables it — so any allocation benchmark run here is measuring the *old* allocator. ### 3.2 `goroutineleak` profile is now GA The goroutine leak profile, experimental in 1.26, is generally available. No `GOEXPERIMENT` needed; the `goroutineleakprofile` GOEXPERIMENT setting is **deleted**. ```go // scrape in-process pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1) ``` Or the `net/http/pprof` endpoint `/debug/pprof/goroutineleak` — which is what pi-go should use, since it already serves pprof on `:6060` under `pi --pprof true`. A goroutine counts as leaked when it is blocked on a concurrency primitive (channel, `sync.Mutex`, `sync.Cond`, …) that the GC proves unreachable from any runnable goroutine or anything those could unblock — so it can never be unblocked. **Known blind spot**: leaks where the primitive is reachable through a global, or through the locals of a runnable goroutine, are *not* detected. Long-lived registries in pi-go fall exactly in that gap, so a clean profile is not proof of no leak. Keep `goleak.VerifyTestMain(m)` in tests. ### 3.3 Goroutine labels in tracebacks For modules with `go 1.27` or later, `runtime/pprof` goroutine labels appear in the traceback header line — in panics, SIGQUIT dumps and `runtime.Stack` output: ``` goroutine 1 [running] {request: 42}: ``` Anything already wrapped in `pprof.Do(ctx, pprof.Labels(...), ...)` now leaks that context into crash output. **Check pi-go's labels for anything sensitive** (session ids are fine; API keys or prompt text are not) before bumping the `go` directive. Opt-out is `GODEBUG=tracebacklabels=0`, added in 1.26 and expected to be kept indefinitely. ### 3.4 Compiler changes with observable effects - Function literal (closure) names are now **simpler and inlining-independent**, and the compiler may share one compiled body across multiple inlined instances. Tests that assert on symbol names may need updating — better, stop asserting on them. - Consequently, **comparing function code pointers for equality is more broken than before**: distinct closures with different captured data can now share a code pointer. Go never defined func comparison beyond `== nil`; 1.27 just exposes it more. - Relative filenames in `//line` / `/*line*/` directives resolve against the containing file's directory ([#70478](https://go.dev/issue/70478)), matching `go/scanner`. --- ## 4. Standard library ### 4.1 `encoding/json/v2` and `encoding/json/jsontext` (new) ```go import ( json "encoding/json/v2" "encoding/json/jsontext" ) data, err := json.Marshal(Point{X: 1, Y: 2}, opts...) // variadic Options err = json.MarshalWrite(w, v, opts...) // to an io.Writer err = json.MarshalEncode(enc, v, opts...) // to a *jsontext.Encoder err = json.Unmarshal(data, &v, opts...) err = json.UnmarshalRead(r, &v, opts...) err = json.UnmarshalDecode(dec, &v, opts...) ``` `jsontext` is the syntactic layer: `Encoder`, `Decoder`, `Token`, `Value`, with a state machine enforcing valid JSON. v2 chooses stricter defaults than v1: it **rejects invalid UTF-8 in strings** and **rejects duplicate object names**. It also **does not sort map keys** (v1 always did) — pass `json.Deterministic(true)` when you need stable output for golden files. **`encoding/json` (v1) is now backed by v2.** Behavior is preserved; **error message text may differ**. If any pi-go test asserts on a JSON error *string*, that is the thing most likely to break — assert on error identity or a substring you control instead. Marshal is at parity; unmarshal is significantly faster. Escape hatch: `GOEXPERIMENT=nojsonv2` (expected to be removed in a future release). **Prescription**: no migration is required and v1 stays supported. Keep pi-go on v1 unless a call site actually wants v2's strictness or its options; do not do a mechanical sweep. ### 4.2 `uuid` (new, top-level stdlib package) Implements RFC 9562, seeded from a cryptographically secure RNG. `UUID` is `[16]byte`, so it is **comparable with `==`** and usable as a map key. ```go id := uuid.New() // suitable for most purposes; currently == NewV4 r := uuid.NewV4() // 122 bits of randomness k := uuid.NewV7() // 48-bit timestamp prefix — sorts by creation time u, err := uuid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") m := uuid.MustParse(s) // panics on bad input uuid.Nil(); uuid.Max() // functions, not vars u.String(); u.Compare(v) // Compare is big-endian, RFC 9562 §6.11 ``` `UUID` implements `encoding.TextMarshaler`, `TextAppender` and `TextUnmarshaler`. **Prescription for pi-go**: drop `github.com/google/uuid` for new code. For anything written to `$HOME/.pi-go/sessions/` or used as a database/session key, prefer `NewV7` — time-ordered ids sort chronologically, which is exactly what the session directory listing wants. ### 4.3 `crypto/mldsa` (new) — post-quantum signatures FIPS 204 ML-DSA, in three parameter sets returned by functions: `mldsa.MLDSA44()`, `MLDSA65()`, `MLDSA87()`. `Parameters` exposes `PublicKeySize()`, `SignatureSize()`, `String()`. ```go priv, err := mldsa.GenerateKey(mldsa.MLDSA65()) sig, err := priv.Sign(rand.Reader, msg, crypto.Hash(0)) err = mldsa.Verify(priv.PublicKey(), msg, sig, nil) ``` `crypto/x509` handles ML-DSA keys and signatures; `crypto/tls` gains the `MLDSA44`, `MLDSA65`, `MLDSA87` `SignatureScheme` values for TLS 1.3. `crypto.MLDSAMu` is a new `Hash` sentinel for external-μ signing (it panics if passed to `RegisterHash`). ### 4.4 `simd` and `simd/archsimd` (experimental) `GOEXPERIMENT=simd` at build time. `simd` is portable and vector-size-**agnostic** (`Int8s`, `Int32s`, `Float32s`, …; width varies by machine), supporting a scalable subset of `simd/archsimd`. `archsimd` is architecture-specific and unstable: 128-bit on wasm/arm64/amd64, 256-/512-bit on some amd64. **Not for pi-go** — no hot vector loops here, and the API is not stable. ### 4.5 New APIs worth using Signatures below were read from the 1.27.0 GOROOT, not from the release notes prose. ```go // strings / bytes — replaces the LastIndex dance func CutLast(s, sep string) (before, after string, found bool) // strings func CutLast(s, sep []byte) (before, after []byte, found bool) // bytes before, after, found := strings.CutLast("a/b/c", "/") // "a/b", "c", true before, after, found = strings.CutLast("nosep", "/") // "nosep", "", false ``` ```go // testing/synctest — time.Sleep + synctest.Wait in one call func Sleep(d time.Duration) ``` ```go // net/http/httptest — in-memory network, no real port, registers t.Cleanup func NewTestServer(t testing.TB, handler http.Handler) *Server ``` ```go // hash/maphash — the contract for future hash-based containers type Hasher[T any] interface { Hash(*Hash, T) Equal(x, y T) bool } type ComparableHasher[T comparable] struct{ /* Equal is == */ } ``` ```go // math/big — quotient and remainder with an explicit rounding mode func (z *Int) Divide(x, y, r *Int, mode RoundingMode) (*Int, *Int) // modes: big.Trunc, big.Floor, big.Round, big.Ceil // 7/2 with Ceil -> q=4, r=-1 // 7/2 with Floor -> q=3, r=1 ``` ```go // math/rand/v2 — the top-level N, now a method (first stdlib generic method) func (r *Rand) N[Int intType](n Int) Int r := rand.New(rand.NewPCG(1, 2)) r.N(100) // int in [0, 100) r.N(5 * time.Second) // works for any integer-kind type, incl. Duration ``` ```go // net/url func (u *URL) Clone() *URL func (vs Values) Clone() Values ``` Also new: `database/sql.ConvertAssign(scanCtx driver.ScanContext, dest any, src driver.Value) error`; `database/sql/driver.RowsColumnScanner`; `go/constant.StringLen(x Value) int64`; `(*go/scanner.Scanner).End() token.Pos`; `(*go/token.File).String()`; `go/types.Hasher` and `HasherIgnoreTags`. **Prescriptions**: - `strings.CutLast` / `bytes.CutLast` replace `LastIndex` + two slice expressions. Grep pi-go for `strings.LastIndex` when touching those call sites. - `synctest.Sleep(d)` replaces the `time.Sleep(d); synctest.Wait()` pair that `code-guidelines-go` §6 currently spells out. Prefer it inside a bubble: when the test and the code under test sleep the same duration, plain `time.Sleep` leaves the wake order unpredictable. - `httptest.NewTestServer(t, h)` binds no TCP port and self-cleans, so `defer srv.Close()` goes away and round-trips can run in synthetic time under `testing/synctest`. Worth trying against the `internal/cli` sandbox trap in `AGENTS.md`, where `httptest.NewServer` panics in `newLocalListener` — but that has not been demonstrated here, so verify before relying on it. - `maphash.Hasher[T]` is a contract for *future* containers; there is no stdlib hash set yet. Use it only if pi-go writes its own hash-based container, and honour the rule that `Equal` values must `Hash` identically. ### 4.6 Behaviour changes that can bite | Package | Change | What to do | |---|---|---| | `net/http` | HTTP/1 `Response.Body` **auto-drains** unread content on `Close` (to a conservative limit) so the connection can be reused | Usually a win. If you `Close` early to abort a large download, set `Transport.DisableKeepAlives = true` | | `net/http` | HTTP/2 server honours **RFC 9218 client priorities** | `Server.DisableClientPriority = true` restores round-robin | | `net/http` | New `Server.MaxHeaderValueCount` caps accepted header values; default `http.DefaultMaxHeaderValueCount` (500) | Set it on pi-go's servers if a client legitimately sends many values | | `net/http` | ALPN negotiation now works on a user-supplied `net.Conn` that has `ConnectionState() tls.ConnectionState` | — | | `compress/flate` | Compression speed improved; **exact encoded bytes differ from 1.26** — propagates to `archive/zip`, `compress/gzip`, `compress/zlib`, `image/png` | Any golden file holding compressed bytes will fail. Regenerate; do not assert on compressed output | | `unicode` | Unicode 15 → **17** | Code points unassigned before (e.g. U+1FADC) now report `IsSymbol`/`IsGraphic` true. Golden files of width-measured or classified text may shift | | `net` | `UnixConn` read methods return `io.EOF` directly instead of wrapping in `net.OpError` | Replace `net.OpError` unwrapping with `errors.Is(err, io.EOF)` | | `crypto/x509` | `SystemCertPool` respects `SSL_CERT_FILE`/`SSL_CERT_DIR` on **Windows and Darwin**, switching to the Go verifier | `GODEBUG=x509sslcertoverrideplatform=0` restores platform APIs | | `crypto/x509/pkix` | `RDNSequence.String` / `Name.String` render string-typed values as strings even for unrecognized OIDs (previously hex DER) — [#33093](https://go.dev/issue/33093) | Certificate-subject golden strings may change | | `crypto/ecdsa` | `PrivateKey.Sign` validates hash length when `SignerOpts` is non-nil | Passing a mismatched digest now errors | | `runtime/secret` | Goroutines created in secret mode now run in secret mode | — | --- ## 5. Deprecations and removals **Removed permanently — these now fail the build or the go command:** | Removed | Since | Replacement / effect | |---|---|---| | `asynctimerchan` GODEBUG | 1.23 | `time` channels are **always unbuffered**. `asynctimerchan=1` is now a hard error; `=0` still parses (§2.2) | | `gotypesalias` GODEBUG | 1.22 | `go/types` always produces an `Alias` node for alias declarations | | `tlsunsafeekm`, `tlsrsakex`, `tls10server` | 1.22 | no escape hatch left | | `tls3des`, `x509keypairleaf` | 1.23 | no escape hatch left | | `goroutineleakprofile` GOEXPERIMENT | 1.26 | the profile is GA (§3.2) | | `bzr` support in the go command | — | vendor the module, or move it | | `fmtappendf` go fix analyzer | — | none (dropped on style grounds) | **Renamed**: go fix `waitgroup` → `waitgroupgo`. **Newly deprecated**: `crypto/tls.Config.Rand` — for deterministic testing use `testing/cryptotest.SetGlobalRandom`. Post-quantum hybrid key exchanges can now be enabled explicitly via `Config.CurvePreferences` even under `tlsmlkem=0` / `tlssecpmlkem=0`; those GODEBUGs were only ever meant to affect the default set used when `CurvePreferences` is nil. **Temporary opt-outs, on a clock** — do not build a workflow on these: `GOEXPERIMENT=nosizespecializedmalloc` (expected gone in 1.28) and `GOEXPERIMENT=nojsonv2` (expected gone in a future release). --- ## 6. Porting and compatibility checklist Work through this when bumping the `go` directive to `1.27`: 1. **`go.mod`**: bump the `go` line in the same change as the first 1.27 stdlib symbol — `go test` now runs `stdversion` and will fail otherwise (§2.1). 2. **`go mod tidy`**: expect the `require` blocks to be consolidated into two (§2.3). Review the diff once, then move on. 3. **GODEBUG audit**: grep `go.mod` `godebug` lines and `//go:debug` comments for `asynctimerchan`, `gotypesalias`, `tlsunsafeekm`, `tlsrsakex`, `tls3des`, `tls10server`, `x509keypairleaf`. Any of them set to a non-default value now fails. 4. **Golden files**: regenerate anything holding DEFLATE/gzip/zip/PNG bytes, JSON error strings, certificate subject strings, or Unicode-classified text (§4.6). 5. **pprof labels**: confirm no `pprof.Labels` value carries a secret before tracebacks start printing them (§3.3). 6. **Symbol-name tests**: closure names changed; func-pointer equality is less reliable (§3.4). 7. **macOS**: 1.27 requires **macOS 13 Ventura or later** — announced in the 1.26 notes. 8. **linux/ppc64 (big-endian)**: now targets the **ELFv2** ABI (kernel 3.13+; RHEL7 backported to 3.10). cgo, PIE and external linking are supported but need an ELFv2-compatible runtime. Pure-Go builds still default to static internal linking; set `CGO_ENABLED=0` for a static binary from a cgo-optioned program. 9. **`//go:linkname`**: 1.27 tightens unsanctioned linknames (a `linknamestd` directive restricts some to the stdlib, and `cmd/link` now checks linkname access to assembly symbols). A dependency reaching into runtime internals is the likely breakage; test the dependency tree early. *(This one is from the VM blog's "hidden gems", not the release notes — treat it as a heads-up to verify, not as a documented contract.)* 10. **Tooling**: `golangci-lint` v2.13.1 parses generic methods and struct-literal field selectors cleanly — verified locally on both constructs, 0 issues, exit 0. --- ## 7. Quick reference | Concern | Go 1.27 | Was | |---|---|---| | Generic op on a concrete container | method with own type params | package-level generic func | | Setting a promoted field | `User{ID: 7}` | `User{Base: Base{ID: 7}}` | | Generic func in a slice literal | `[]func([]int) int{first, last}` | `{first[int], last[int]}` | | Split on last separator | `strings.CutLast` / `bytes.CutLast` | `strings.LastIndex` + slicing | | Sleep inside a synctest bubble | `synctest.Sleep(d)` | `time.Sleep(d); synctest.Wait()` | | HTTP test server | `httptest.NewTestServer(t, h)` | `httptest.NewServer(h)` + `defer Close()` | | UUIDs | stdlib `uuid` (`NewV7` for keys) | `github.com/google/uuid` | | Bounded random from own source | `r.N(100)` | `rand.N(100)` (global) | | Rounded integer division | `big.Int.Divide(x, y, r, big.Floor)` | `Quo`/`Mod` + manual adjust | | Deep-copy a URL | `u.Clone()` | manual struct copy | | Leaked goroutines in prod | `/debug/pprof/goroutineleak` | experiment / `goleak` in tests only | | Deterministic JSON map order | `json.Deterministic(true)` (v2) | implicit in v1 | | `wg.Add`/`Done` modernizer | `go fix -waitgroupgo` | `-waitgroup` |