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
}
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:
[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*[N]T
, which you can't easily abstract in a type constraintK
and values V
, which may or may not be the same as T
. Plus, K
must implement comparable
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With