I am trying to write a type constraint for a Go program, that accepts "anything you can take len() of". But I can't really figure it out.
I want something like:
LenOf[m Measurable](m M) int {
return len(m)
}
I tried a few things. Fx. this naiive thing, which do compile, but doesn't work on all types (like fx []User
):
type Measurable interface {
~string | []any | ~map[any]any
}
Then went on to something like, the below which not only makes the function signature for LenOf()
extremely clunky, but also have clumsy to write on call sites (and still can't get it to compile)
type Measurable[K comparable, V any] interface {
~string | []V | ~map[K]V
}
CodePudding user response:
Why? The builtin len
is already "generic".
With that said, let's see why defining such a constraint is a bad idea. The Go spec has a paragraph — Length and capacity, that can help:
If the argument type is a type parameter P, the call
len(e)
(orcap(e)
respectively) must be valid for each type in P's type set. The result is the length (or capacity, respectively) of the argument whose type corresponds to the type argument with which P was instantiated.
The issues with writing an all-encompassing constraint for "measurable" types are:
- it includes arrays
[N]T
, where the array length is part of the type, so your constraint would have to specify all possible arrays you want to capture - it includes array pointers
*[N]T
, which you can't easily abstract in a type constraint - it includes maps, which forces you to capture keys
K
and valuesV
, which may or may not be the same asT
. Plus,K
must implementcomparable
.
So you'd have to write something like:
type Measurable[T any, K comparable, V any] interface {
~string | ~[]T | ~map[K]V | ~chan T
}
which notably doesn't include arrays, and doesn't distinctly capture pointer literals, e.g. to match []*int
, you would have to instantiate T
with *int
.
You might simplify V
away:
type Measurable[T any, K comparable] interface {
~string | ~[]T | ~map[K]T | ~chan T
}
The function LenOf
then becomes:
func LenOf[T any, K comparable, M Measurable[T, K]](m M) int {
return len(m)
}
but you still have to supply K
, so you have to instantiate LenOf
at call site with bogus types for the map key:
LenOf[string, int]("foo")
// ^ actually useless
and you can't take advantage of type inference with the argument either.
In conclusion: just use len
. Design your generic functions to work with type literals that support length, or add to the constraint only those types that your functions are reasonably expected to handle.