Wandering Thoughts archives

2022-02-12

The 'any' confusion in Go generics between type constraints and interfaces

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] []T
type 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]  // okay
var 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.

programming/GoGenericsTypeInterfaceIssue written at 22:14:44; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.