Почему байтовый срез не удовлетворяет общему ограничению типа любого среза?

У меня есть следующий код с двумя универсальными функциями. Они оба выполняют простую операцию добавления фрагмента к себе и возврата результата. Однако вызов более простой и понятной функции cat1 приводит к ошибке компиляции, в то время как вызов более неуклюже определенной функции cat2 с ее лишним и явным объявлением элемента среза E работает, как и ожидалось. Почему cat1 недопустимо?

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"))))
}

Ошибка при вызове cat1:

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

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

🤔 А знаете ли вы, что...
Go разрабатывался с учетом производительности и эффективности.


2
75
1

Ответ:

Решено

Ограничения типа должны быть интерфейсами, однако ключевое слово interface почти всегда опускается как синтаксический сахар.

Поэтому, когда вы используете ~[]any в качестве ограничения типа, вы на самом деле пишете следующее:

interface{ ~[]any }

В этом интерфейсе ~[]any — это термин типа. Какие типы могут удовлетворять этому ограничению? Как и все ограничения:

Аргумент типа T удовлетворяет ограничению типа C, если T является элементом набора типов, определенного C.

Набор типов, определенный вашим ограничением C, он же interface{ ~[]any }, — это литерал типа []any и все типы с []any в качестве базового типа. Например:

type Foo []any

[]byte не включен в набор типов interface{ ~[]any }, точно так же, как вне общего кода вы не можете присвоить []byte[]any:

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

Как упомянул @JimB в комментарии, byte и any имеют разные макеты памяти, поэтому их составные типы не являются взаимоназначаемыми.

Теперь, когда вы используете только any в качестве ограничения типа, все меняется. any — это псевдоним interface{} (пустой интерфейс). В этом случае синтаксический сахар отсутствует. Ограничение — это просто interface{}, который представляет собой интерфейс без методов и типов. Это реализуют все типы, включая byte.

Ваша вторая функция cat2 — единственный правильный способ определить параметр типа S, который может быть любым срезом:

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

Здесь E ограничено любым типом, а S разрешается как часть того, чем E является.