deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Go 1.26 lets new() take expressions, clearing the way to delete pointer helpers

Go 1.26 lets the built-in new() accept expressions like new(42) or new(time.Now()), and a new go fix modernizer can rewrite most hand-rolled pointer helpers automatically.

Go 1.26 lets new() take expressions, clearing the way to delete pointer helpers

The Go 1.26 release, published on February 10, 2026, lets the built-in new() accept an expression as its argument instead of only a type, according to a dev.to post. Calls such as new(42) and new(time.Now()) now compile, and a bundled go fix modernizer can rewrite most hand-rolled pointer helpers automatically.

A decade-old gap

Go always made it easy to get a pointer to a composite literal with &S{a: 3}, but the address of a constant or of a function call's result could not be taken, so &42 never compiled. Since the language has no optional type, pointers are the standard way to distinguish "field unset" from "field is zero" when serializing with encoding/ or protocol buffers; the release notes, quoted in the post, single the feature out as especially handy for serialization packages that use a pointer to mark an optional value.

The result was duplicated one-line helpers everywhere — ptr[T], StringPtr, the proverbial utils/pointer.go — plus ecosystem copies such as proto.String and proto.Int64 in protobuf-go, ptr.To in k8s.io/utils and ToPtr in samber/lo. The gap itself is old: Rob Pike filed proposal 45624 in April 2021 as an alternative to the older 9097, and Alan Donovan closed the issue in October 2025, joking that the work was finished a mere eleven years after the original request.

How the new form behaves

Per the updated spec language cited in the post, when the argument is an expression x, new allocates a variable of x's type initialized to x's value. new(int) still returns a pointer to a zero int; new(123) returns a pointer to 123; both are *int.

To use the syntax, a module — or a single file via a build constraint — must declare Go 1.26 or later, for example with go mod edit -go=1.26. The post notes that bumping go.mod raises the minimum Go version for anyone importing the module, which is usually fine for applications but a genuine decision for libraries. Go 1.27, out since August 19, 2026, changed nothing here, so either current release works.

go fix does the sweep

Go 1.26 also rebuilt go fix as the home of the language's modernizers, on the same analysis framework as go vet. One of them, newexpr, targets exactly this pattern. It looks for what the analyzer calls new-like functions — one non-variadic parameter, one pointer result, a body that does nothing but return the parameter's address — rewrites the helper body to return new(x) with an inline directive, and rewrites call sites like ptr("dark") to new("dark").

Two caveats apply. The fixer only touches files that already require Go 1.26, so bump go.mod first or the diff comes back empty. And the Go blog, cited in the post, recommends running go fix repeatedly until it reaches a fixed point; twice is usually enough. The author adds that they have not run the sweep themselves, so the -diff preview is the source of truth for any given repository. Afterward, delete the helpers and run go build ./... — the leftover compile errors form the manual review queue.

What stays manual

The fixer skips any rewrite that would change a call's type. An untyped constant passed to new takes its default type, so new(42) is *int; a *int64 field therefore needs an explicit conversion, new(int64(5000)). The post concedes that proto.Int64(5000) can read better than that when a protobuf message has many int64 fields, so keeping some helpers is defensible. Also left alone are scopes that shadow new with their own declaration, and untyped composite literals. And because built-in functions cannot be used as function values, new cannot be passed where a func(T) *T is expected — the clearest remaining case for a helper.

Why it matters

This closes one of Go's longest-running ergonomic complaints and lets teams delete boilerplate that existed only to work around the language. The semantic payoff is real: a nil pointer versus a pointer to 0 encodes "absent" versus "explicitly zero" in JSON and protobuf, and writing that distinction is now a built-in rather than an import. Just as notable is the delivery mechanism — by pairing the language change with a vet-backed go fix modernizer, the Go team is demonstrating how future syntax improvements can be rolled out to existing codebases mechanically instead of by hand.

  • #go
  • #programming-languages
  • #pointers
  • #static-analysis
  • #developer-tools

Related posts