Any system of generic types, such as Go will have in Go 1.18, needs some way to specify constraints on the specific types that generic code can take. Go uses what it calls "type sets" , which reuse Go's existing interface types with some extensions. However, this reuse creates a potential for confusion, one that I've already seen come up in some articles about Go generics such as this one ( via ).
Suppose that you have some generic types and code (and interfaces):
type Result[T any] []Ttype Fredable[T any] interface { Fred(s string) T } type Barable interface { Bar() uint64 } type Addable interface { ~uint | ~float64 }
The
Barable
type is an interface type that can be used today in Go 1.17, but it's also usable as a generic type constraint in a way that's different from just having a function that takes arguments with the interface type:
func DoBar[T Barable](a, b T) uint64 {
return a.Bar() + b.Bar()
}
(Among other things, this generic function requires
a
and
b
to have the same type when it's instantiated to a specific type.)
Now consider the following set of declarations using the
Result
generic type that creates a slice of the given type:
var r1 Result[Barable] // okayvar r2 Result[Fredable] // error var r3 Result[Addable] // error var r4 Result[any] // okay. What?
The type of
r1
is a slice of
Barable
interface values.
Barable
is a regular interface type and you can declare slices of interface types, which contain interface values of that (interface) type. You cannot declare
r2
or
r3
because although they are both declared using the
type
and
interface
keywords, neither
Fredable
nor
Addable
are normal interface types. They're only usable as type constraints, and the
Result
generic type needs a type, not a type constraint.
The potentially confusing case is the last one, '
Result[any]
'. Right now, '
any
' is new syntax that generally only shows up in articles about Go generics, as a type non-constraint that means 'any type is acceptable'. However, it's an alias for '
interface{}
', the universal interface. Used in or as a type constraint, it means that there is no constraint on the types that the generic type can be used with (you can make a slice of anything). Used as a regular type, though, it means what it usually means, so
r4
is a slice of '
interface{}
' values (and you'll be able to add anything to it, because anything can be converted to an '
interface{}
' value).
My personal view is that it might be simpler if '
any
' was only accepted in type constraints and couldn't be used as a regular interface type. This is already the case with the '
comparable
' type constraint, which doesn't map naturally to something in normal, non-generic Go. If '
any
' could only be used in type constraints, I think '
interface{}
' should still be accepted as equivalent to '
any
' there. But I understand why the Go developers did '
any
' this way, especially since the type sets approach requires '
interface{}
' to be equivalent to it.
As a side note, because
Fredable
takes a type parameter and can be instantiated to become a specific type, we can do a version of this with additional work. We can write:
var r5 Result[Fredable[string]]
However, there's no way to use
Addable
this way. The Go compiler error messages will tell us this, because we get different ones in each case. Currently this is:
cannot use generic type Fredable[T any] without instantiation interface contains type constraints
(The error messages might change before Go 1.18 is released.)
The type of
r5
is also different and more specific than you might want, although there is a whole question of what '
Result[Addable]
' or '
Result[Fredable]
' would really mean if Go accepted it. A full discussion of that is for another entry.