返回资讯中心
外部精选
后端
#Go

Generic Methods

Go 1.27 adds generic methods—a highly desired language feature.

Go BlogMark Freeman21 分钟阅读

以下正文同步自 Go Blog,版权归原站所有,已转换为易读排版。

The Go Blog

Generic Methods

Mark Freeman

26 August 2026

Generics was a profound change for Go—it fundamentally expanded the kinds of

programs that gophers could write by adding type parameters to the language.

One could now express generic types and functions in Go. These constructs

decreased the need for specialized data types and functions, reducing program

verbosity and improving the ergonomics of the language in those cases.

To illustrate, different kinds of linked lists could now be condensed into a

single type definition:

// before Go 1.18

type ListOfInts struct {

elem int

next *ListOfInts

}

type ListOfStrings struct {

elem string

next *ListOfStrings

}

// ...

// after Go 1.18

type List[E any] struct {

elem E

next *List[E]

}

Likewise, sorting different kinds of ordered data could be expressed as a single

function:

// before Go 1.18

func SortInts(s []int) { /* ... */ }

func SortStrings(s []string) { /* ... */ }

// ...

// after Go 1.18

func Sort[E cmp.Ordered](s []E) { /* ... */ }

However, no such capability came to methods.

The generics proposal reasoned that because generic interface

methods are difficult to implement efficiently (discussed

later), it didn’t make sense to

add type parameters to non-interface (or “concrete”) methods either. In that

view, methods are primarily the means of implementing an interface.

Go 1.27 takes a different view and consequently adds generic methods to Go. In

this post, we’ll explain this change of view and demonstrate some uses of this

new feature.

Methods for organization

Methods enable organizing functionality around types. To illustrate, let’s

reconsider the linked list from above (adding some conveniences):

type List[E any] struct {

elem E

next *List[E]

}

func NewList[E any](elems ...E) List[E] { /* ... */ }

func (List[E]) String() string { /* ... */ }

Suppose that one wanted to map this structure to hold a value of some other

type, like a string. A method like ToString would do:

func (List[E]) ToString(f func(E) string) List[string] { /* ... */ }

By providing a “transform” function f, ToString can be customized with

“off-the-shelf” routines.

For example, strconv.Itoa might work for a List[int]:

func main() {

fmt.Println(NewList(1, 2, 3).ToString(strconv.Itoa)) // [1 2 3]

}

For a List[[]byte], more options come to mind (depending on the use case):

func main() {

l := NewList([]byte("Hallo Welt"), []byte("Helló világ"))

fmt.Println(l.ToString(hex.EncodeToString)) // [48616c6c6f2057656c74 48656c6cc3b32076696cc3a167]

fmt.Println(l.ToString(base64.StdEncoding.EncodeToString)) // [SGFsbG8gV2VsdA== SGVsbMOzIHZpbMOhZw==]

}

Note that parameterizing List allowed generalization of the source type, but

not the destination type, since that depends on the transformation being

applied. This might be reasonable with just a single destination type, but what

if there were many?

In Go 1.18, a generic function could be used for this:

// after Go 1.18

func MapList[E, R any](l List[E], f func(E) R) List[R] { /* ... */ }

But using a function has the disadvantage of moving MapList into the package

scope—if many data types support mapping operations, things could get crowded.

Furthermore, chained calls must be written “inside out”:

func main() {

fmt.Println(MapList(MapList(NewList(0, 2, 4), add(2)), divideBy(2))) // [1 2 3]

}

Because Go 1.18 didn’t support generic methods, we needed a generic function as

a workaround. This is now remedied in Go 1.27:

// after Go 1.27

func (List[E]) Map[R any](f func(E) R) List[R] { /* ... */ }

This is compact, expressive, and locally scoped. Additionally, chained calls

are more readable, as they can be written more naturally from left to right:

func main() {

fmt.Println(NewList(0, 2, 4).Map(add(2)).Map(divideBy(2))) // [1 2 3]

}

Prefer the “inside out” form? A method expression can convert any method,

including a generic one, into its function equivalent. Thus, a function call

structure can be recovered if desired:

func main() {

f := List[int].Map[int]

fmt.Println(f(f(NewList(0, 2, 4), add(2)), divideBy(2))) // [1 2 3]

}

Generic methods, like other generics in Go, must be instantiated (either

explicitly or implicitly) before they are used (called or converted to a

function).

Decoupling things

If one views methods also as an organizational tool, then Go 1.18’s reasoning

appears overly restrictive. While a generic concrete method can’t help implement

an interface (without generic interface methods), it can still be useful for

code organization. In other words, these concerns can be decoupled.

To elaborate, let’s consider a simple interface:

type I interface {

M()

}

type T struct{} // a struct, not an interface

func (T) M[P any]() { /* ... */ } // a generic *concrete* method

Here, T.M is parameterized by P. Any instantiation of T.M would result in

a method signature identical to that of I.M:

func main() {

T{}.M[int]()

}

Both T.M[int] and I.M have the signature func M(). However, this doesn’t

mean T implements I. Interface implementation is a property of a (possibly

instantiated) type, not of any particular method. Importantly, T does not

declare T.M[int], but rather its generic (and thus uninstantiated) counterpart

T.M.

For T.M to participate in interface implementation, there would need to exist

a suitable generic method I.M:

type I interface {

M[P any]() // a generic *interface* method

}

type T struct{}

func (T) M[P any]() { /* ... */ }

Since the Go 1.27 syntax doesn’t allow interface methods to declare type

parameters, this code is impossible to write.

But why can’t Go have generic interface methods? To understand, a bit of a

detour is needed.

The trouble with generic interface methods

An interface value is a box that can hold some other value—the boxed value can

be of any type, as long as that type implements the interface. This means that

the declared methods on the type are a superset of those declared on the

interface.

To illustrate, T implements I below:

type I interface {

M()

}

type T struct{}

func (T) M() { /* ... */ }

One can call methods on interface values:

func main() {

F(T{})

}

func F(i I) {

i.M() // "backed" by T.M

}

Above, it’s easy to observe that the call to i.M will be routed to T.M. But

in more complex programs, the relation between an interface value method call

and its possible targets is significantly less obvious. This relation

typically spans across packages, making it difficult or impossible to deduce in

the general case.

To illustrate, let’s introduce a package boundary:

// -- package main --

import "p"

func main() {

p.F(T{})

}

type T struct{}

func (T) M() { /* ... */ }

// -- package p --

func F(i I) {

i.M()

}

type I interface {

M()

}

Because the two packages are compiled separately, the main package won’t know

how p.F uses T{}. To ensure the existence of any method that p.F might

call, the compiler generates code for all non-generic methods of a type at its

declaration (or instantiation). That way, regardless of what p.F does with

T{}, the necessary code will exist at runtime.

Now, suppose that I.M were generic:

// -- package main --

import "p"

func main() {

p.F(T{})

}

type T struct{}

func (T) M[P any]() { /* ... */ }

// -- package p --

func F(i I) {

i.M[int]()

}

type I interface {

M[P any]()

}

Again, the main package won’t know how p.F uses T{}—including how it might

instantiate T.M. To handle any usage of T{} inside p.F, the compiler

would need to instantiate T.M with every possible type argument.

This is impractical with Go’s approach to instantiation, wherein the compiler

generates specific code for each method based on the type arguments. If method

arguments were “boxed” (i.e. passed as values of their constraint interfaces),

each instantiation would share the same code. This would avoid having too many

instantiations at the cost of indirect call overhead—even for direct calls to

instantiated methods.

Conclusion

Go 1.27 introduces type parameters on concrete methods—this feature has been

highly desired by the Go community, as it permits more ergonomic and readable

code. Although we can’t support generic interface methods, we decided that

allowing type parameters for concrete methods was still worthwhile.

We hope you enjoy using Go’s generic methods and find helpful ways to use them

in your projects!

Next article: Goroutine Leak Profiles

Previous article: Go 1.27 is released

Blog Index

正文由 FLUX 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。