gogenericsslicetype-constraints

Why doesn't a byte slice satisfy a generic type constraint of an any slice?


I have the following code with two generic functions. They both do the simple operation of appending a slice to itself and returning the result. However, invoking the simpler and more straightforward function cat1 produces a compilation error while invoking the more clumsily defined function cat2 with its superfluous and explicit declaration of the slice element E works as expected. Why isn't cat1 acceptable?

func cat1[S ~[]any](s S) S {
  return append(s, s...)
}

func cat2[S ~[]E, E any](s S) S {
  return append(s, s...)
}

func main() {
  fmt.Println(string(cat2([]byte("hello"))))
}

The error when invoking cat1 is:

[]byte does not satisfy ~[]any ([]byte missing in ~[]any)

https://go.dev/play/p/IJzCEiPglgA


Solution

  • Type constraints must be interfaces, however the keyword interface is almost always omitted as a syntactic sugar.

    Therefore when you use ~[]any as a type constraint, what you're actually writing is:

    interface{ ~[]any }
    

    In this interface, ~[]any is a type term. What types can satisfy this constraint? As with all constraints:

    A type argument T satisfies a type constraint C if T is an element of the type set defined by C

    The type set defined by your constraint C a.k.a. interface{ ~[]any } is the type literal []any and all types with []any as underlying type. For example:

    type Foo []any
    

    []byte is not included in the type set of interface{ ~[]any }, just like outside of generic code you couldn't assign a []byte to an []any:

    var a []byte
    var b []any
    b = a // compilation error
    

    As @JimB mentioned in a comment, byte and any have different memory layouts, therefore their composite types aren't mutually assignable.

    Now when you use any alone as a type constraint, things change. any is an alias of interface{} (the empty interface). In this case there is no syntactic sugar. The constraint is just interface{} which is an interface with no methods and no type terms. All types implement it, including byte.

    Your second function cat2 is the only correct way to define a type parameter S that can be any slice:

    func cat2[S ~[]E, E any](s S) S {
      return append(s, s...)
    }
    

    Here E is constrained to any type, and S resolves to a slice of whatever E is.