Go

Why do the slices package functions use the constraint [S ~[]E, E any] instead of just []E?

Question 53HardGo 1.22 to 1.25

If a function is declared as func Filter[E any](s []E) []E, then passing a named slice type like type IDs []int returns plain []int. The caller loses the named type and its methods (for example, a sort.Interface implementation). The constraint S ~[]E means "any type whose underlying type is []E". The function then returns S and the caller's type is kept.

type IDs []int

func (ids IDs) String() string { return fmt.Sprint([]int(ids)) }

func FilterLossy[E any](s []E, keep func(E) bool) []E { /* ... */ return s }

func Filter[S ~[]E, E any](s S, keep func(E) bool) S {
	out := s[:0:0] // zero-length, zero-cap, same type S
	for _, v := range s {
		if keep(v) {
			out = append(out, v)
		}
	}
	return out
}

func main() {
	ids := IDs{1, 2, 3, 4}
	even := Filter(ids, func(v int) bool { return v%2 == 0 })
	fmt.Printf("%T %v\n", even, even) // main.IDs [2 4]
	// FilterLossy(ids, ...) would return []int, losing the String method
}

Type inference derives E from S automatically ("constraint type inference"), so callers never write the type parameters. This is exactly how slices.Clone, slices.Compact, slices.Insert and similar are declared. Similarly, maps.Clone uses [M ~map[K]V, K comparable, V any].

Interviewer signal: you understand the ~ tilde (underlying-type sets) and why it matters for API design.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions