
Разрушение и объяснение параметров типа
12 июля 2025 г.Подписывания функций пакета срезов
Аslices.Clone
Функция довольно проста: он делает копию кусочка любого типа.
func Clone[S ~[]E, E any](s S) S {
return append(s[:0:0], s...)
}
Это работает, потому что добавление к ломтике с нулевой емкостью распределяет новый массив поддержки. Функциональное тело становится короче, чем функциональная подпись, которая отчасти потому, что тело короткое, но также потому, что подпись длинная. В этом сообщении мы объясним, почему подпись написана так, как она есть.
Простой клон
Мы начнем с написания простого общегоClone
функция Это не тот, что вslices
упаковка. Мы хотим взять кусочек любого типа элемента и вернуть новый кусочек.
func Clone1[E any](s []E) []E {
// body omitted
}
Общая функцияClone1
имеет параметр одного типаE
Полем Требуется один аргументs
который является кусочком типаE
, и он возвращает кусок того же типа. Эта подпись проста для всех, кто знаком с дженерами в Go.
Однако есть проблема. Названные типы кусочков не распространены в ходе, но люди используют их.
// MySlice is a slice of strings with a special String method.
type MySlice []string
// String returns the printable version of a MySlice value.
func (s MySlice) String() string {
return strings.Join(s, "+")
}
Допустим, мы хотим сделать копиюMySlice
А затем получите печатную версию, но с струнами в отсортированном порядке.
func PrintSorted(ms MySlice) string {
c := Clone1(ms)
slices.Sort(c)
return c.String() // FAILS TO COMPILE
}
К сожалению, это не работает. Компилятор сообщает об ошибке:
c.String undefined (type []string has no field or method String)
Мы можем увидеть проблему, если мы вручную экземплярClone1
заменив параметр типа на аргумент типа.
func InstantiatedClone1(s []string) []string
АGO Правила назначенияПозвольте нам передать значение типаMySlice
к параметру типа[]string
, так что звонокClone1
в порядке. НоClone1
вернет значение типа[]string
, не значение типаMySlice
Полем Тип[]string
нетString
Метод, поэтому компилятор сообщает об ошибке.
Гибкий клон
Чтобы решить эту проблему, мы должны написать версиюClone
Это возвращает тот же тип, что и его аргумент. Если мы сможем это сделать, то когда мы звонимClone
со значением типаMySlice
, он вернет результат типаMySlice
Полем
Мы знаем, что это должно выглядеть примерно так.
func Clone2[S ?](s S) S // INVALID
ЭтотClone2
Функция возвращает значение, которое такое же тип, что и его аргумент.
Здесь я написал ограничение как?
, но это просто заполнитель. Чтобы сделать эту работу, нам нужно написать ограничение, которое позволит нам написать тело функции. ДляClone1
Мы могли бы просто использовать ограничениеany
для типа элемента. ДляClone2
это не сработает: мы хотим, чтобы это требовалоs
быть типом среза.
Поскольку мы знаем, что хотим кусочек, ограничениеS
должен быть кусочек. Нам не волнует, что такое тип элемента среза, так что давайте просто назовем этоE
, как мы делали сClone1
Полем
func Clone3[S []E](s S) S // INVALID
Это все еще недействительно, потому что мы не объявилиE
Полем Тип аргумент дляE
может быть любой тип, что означает, что он также должен быть самой параметром типа. Поскольку это может быть любой тип, его ограничениеany
Полем
func Clone4[S []E, E any](s S) S
Это приближается, и, по крайней мере, это будет компилироваться, но мы еще не совсем там. Если мы скомпилируем эту версию, мы получим ошибку, когда звонимClone4(ms)
Полем
MySlice does not satisfy []string (possibly missing ~ for []string in []string)
Компилятор говорит нам, что мы не можем использовать аргумент типаMySlice
Для параметра типаS
, потому чтоMySlice
не удовлетворяет ограничению[]E
Полем Это потому, что[]E
В качестве ограничения только разрешает литерал типа среза, как[]string
Полем Это не позволяет названному типу, какMySlice
Полем
Основные ограничения типа
Как намекает сообщение об ошибке, ответ должен добавить~
Полем
func Clone5[S ~[]E, E any](s S) S
Повторить, написание параметров и ограничений типа[S []E, E any]
означает, что аргумент типа дляS
может быть любой неназванный тип среза, но это не может быть названным типом, определяемым как литерал с ломтиком. Письмо[S ~[]E, E any]
, с~
, означает, что аргумент типа дляS
может быть любой тип, основной тип которого является тип среза.
Для любого названного типаtype T1 T2
основной типT1
является основным типомT2
Полем Базовый тип предварительного типа, подобногоint
или тип буквального подобного[]string
это просто сам тип. Для точных деталей,Смотрите языковую спецификациюПолем В нашем примере основной типMySlice
является[]string
Полем
Поскольку основной типMySlice
это ломтик, мы можем передать аргумент типаMySlice
кClone5
Полем Как вы могли заметить, подписьClone5
такой же, как и подписьslices.Clone
Полем Мы наконец добрались до того места, где хотим быть.
Прежде чем мы продолжим, давайте обсудим, почему синтаксис GO требует~
Полем Может показаться, что мы всегда хотели бы разрешить прохождениеMySlice
, так почему бы не сделать это по умолчанию? Или, если нам нужно поддерживать точное соответствие, почему бы не перевернуть вещи, чтобы ограничение[]E
разрешает названный тип, в то время как, скажем, ограничение, скажем,=[]E
, только разрешает литералы типа среза?
Чтобы объяснить это, давайте сначала заметим, что список параметров типа, как[T ~MySlice]
не имеет смысла. Это потому, чтоMySlice
не является основным типом любого другого типа. Например, если у нас есть определение, подобноеtype MySlice2 MySlice
, основной типMySlice2
является[]string
, нетMySlice
Полем
Так тоже[T ~MySlice]
не допускает никаких типов вообще, или это будет таким же, как[T MySlice]
и только совпадатьMySlice
Полем В любом случае,[T ~MySlice]
бесполезно. Чтобы избежать этой путаницы, язык запрещает[T ~MySlice]
и компилятор производит ошибку, подобную
invalid use of ~ (underlying type of MySlice is []string)
Если GO не потребует тилде, так что это[S []E]
соответствовал бы любому типу, основной тип которого[]E
, тогда нам придется определить значение[S MySlice]
Полем
Мы могли бы запретить[S MySlice]
, или мы могли бы сказать, что[S MySlice]
только совпадаютMySlice
, но любой подход сталкивается с неприятностями с предшественниками типов. Предопределенный тип, какint
, является его собственным основным типом. Мы хотим позволить людям иметь возможность писать ограничения, которые принимают любой аргумент, основной тип которогоint
Полем На языке сегодня они могут сделать это, написав[T ~int]
Полем Если нам не требуется тильда, нам все равно понадобится способ сказать «любой тип, чей основной типint
”. Естественный способ сказать, что это будет[T int]
Полем Это будет означать, что[T MySlice]
и[T int]
будет вести себя по -другому, хотя они выглядят очень похожими.
Возможно, мы могли бы сказать, что[S MySlice]
соответствует любому типу, основной тип которого является основной типMySlice
, но это делает[S MySlice]
ненужный и запутанный.
Мы думаем, что лучше требовать~
И будьте очень ясны, когда мы сопоставляем базовый тип, а не сам тип.
Тип вывод
Теперь, когда мы объяснили подписьslices.Clone
, давайте посмотрим, как на самом деле использоватьslices.Clone
упрощается с помощью типового вывода. Помните, подписьClone
является
func Clone[S ~[]E, E any](s S) S
Звонокslices.Clone
передаст ломтик параметраs
Полем Простой вывод типа позволит компилятору сделать вывод, что аргумент типа для параметра типаS
тип среза, передаваемого вClone
Полем Тип вывод является тогда достаточно мощным, чтобы увидеть, что аргумент типа дляE
Является ли тип элемента аргумента, передаваемогоS
Полем
Это означает, что мы можем написать
c := Clone(ms)
без необходимости писать
c := Clone[MySlice, string](ms)
Если мы обратимся кClone
Не назвав это, мы должны указать аргумент типа дляS
, поскольку у компилятора нет ничего, что он может использовать, чтобы сделать это. К счастью, в этом случае вывод типа способен вывести аргумент типа дляE
от аргументаS
, и нам не нужно указывать это отдельно.
То есть мы можем написать
myClone := Clone[MySlice]
без необходимости писать
myClone := Clone[MySlice, string]
Деконструкция параметров типа
Общая техника, которую мы использовали здесь, в которой мы определяем один параметр типаS
Использование другого параметра типаE
, это способ деконструировать типы в общих подписях функции. Деконструируя тип, мы можем назвать и ограничить все аспекты типа.
Например, вот подпись дляmaps.Clone
Полем
func Clone[M ~map[K]V, K comparable, V any](m M) M
Так же, как сslices.Clone
, мы используем параметр типа для типа параметраm
, а затем деконструируйте тип, используя два других параметров типаK
иV
Полем
Вmaps.Clone
Мы ограничиваемK
быть сопоставимым, как требуется для типа ключа карты. Мы можем ограничить типы компонентов, как нам нравится.
func WithStrings[S ~[]E, E interface { String() string }](s S) (S, []string)
Это говорит о том, что аргументWithStrings
Должен быть тип среза, для которого тип элемента имеетString
метод
Поскольку все типы GO могут быть созданы из типов компонентов, мы всегда можем использовать параметры типа для деконструкции этих типов и ограничить их, как нам нравится.
Ян Лэнс Тейлор
ФотоРобин Джонатан ДойчнаНеспособный
Эта статья доступна на
Оригинал