'In Go generics, how to use a common method for types in a union constraint?
I'm trying to understand the usage of the type union constraint in Go generics (v1.18). Here is the code I tried:
type A struct {
}
type B struct {
}
type AB interface {
*A | *B
}
func (a *A) some() bool {
return true
}
func (b *B) some() bool {
return false
}
func some[T AB](x T) bool {
return x.some() // <- error
}
The compiler complains:
x.someundefined (typeThas no field or method some)
Why is that? If I cannot use a shared method of type *A and *B, what's the point of defining types union *A | *B at all?
(Apparently I can define an interface with the shared method and directly use that. But in my particular use case I want to restrict to the certain types explicitly.)
Solution 1:[1]
Add the method to the interface constraint, without forgoing generics:
type AB interface {
*A | *B
some() bool
}
func some[T AB](x T) bool {
return x.some() // works
}
This restricts T to types that are either *A or *B and declare some() bool method.
However, as you already found out, this is a workaround. You are right that it should work with the type union alone. It's a limitation of Go 1.18. The confusing part is that the language specifications still seem to support your theory (Method sets):
The method set of an interface type is the intersection of the method sets of each type in the interface's type set (the resulting method set is usually just the set of declared methods in the interface).
This limitation appears to be documented only in the Go 1.18 release notes:
The current generics implementation has the following limitations:
[...] The Go compiler currently only supports calling a method
mon a valuexof type parameter typePifmis explicitly declared byP's constraint interface. [...] even thoughmmight be in the method set ofPby virtue of the fact that all types inPimplementm. We hope to remove this restriction in Go 1.19.
The relevant issue in the Go tracker is #51183, with Griesemer's confirmation and the decision to leave the language specifications as is, and document the restriction.
Solution 2:[2]
Change the declaration of AB to
type AB interface {
*A | *B
some() bool
}
In Generic Go, constraints are interfaces. A type argument is valid if it implements its constraints.
Please watch the Gophercon videos on Generics for a better understanding:
- Gophercon 2021: Robert Griesemer & Ian Lance Taylor - Generics!
- Gophercon 2020: Robert Griesemer - Typing [Generic] Go
To ensure that I understood your question please run the code snippet below in Go Playground in “Go Dev branch” mode:
// You can edit this code!
// Click here and start typing.
package main
import "fmt"
type A struct {
}
type B struct {
}
type C struct{}
type AB interface {
*A | *B
some() bool
}
func (a *A) some() bool {
return true
}
func (b *B) some() bool {
return false
}
func (c *C) some() bool {
return false
}
func some[T AB](x T) bool {
return x.some()
}
func main() {
p := new(A)
fmt.Println(some(p))
//uncomment the lines below to see that type C is not valid
//q := new(C)
//fmt.Println(some(q))
}
Solution 3:[3]
I think the old interface{} is enough to do this.
Like this:
type AB interface {
some() bool
}
But if you want to use generic, you must change the type first.
Like this:
func some[T AB](x T) bool {
if a, ok := interface{}(x).(*A); ok {
return a.some()
}
return (*B)(x).some()
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Vanessa Andre |
| Solution 3 | Rahmat Fathoni |
