Я устал писать ручные преобразования слайсов типа []int32 -> []int64, потому что есть много ситуаций, когда вам нужно использовать слайс с другим типом. Поэтому я попытался написать для этого общую функцию:
func convertSlice[T1 any, T2 any](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
и хочу использовать его как
a := []int{1, 2, 3, 4, 5}
var b []int64 = convertSlice[int, int64](a)
Но я не могу его скомпилировать, компилятор говорит, что ./prog.go:8:14: cannot convert t1[i] (variable of type T1 constrained by any) to type T2
Итак, как я могу это исправить?
Живой пример: https://go.dev/play/p/YYOLFjYt4mq
Конечно, я могу написать отдельные функции для каждого примитивного типа, например:
func convertNumericSlice[T1, T2 constraints.Integer | constraints.Float](t1 []T1) []T2 {}
func convertStringSlice[T1, T2 ~string](t1 []T1) []T2 {}
но это совсем не похоже на крутой общий способ.
Если у вас есть тип со всеми типами чисел, то между ними можно преобразовать.
Попробуйте, как показано ниже:
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
func convertSlice[T1 Number, T2 Number](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
Поиграйте в демонстрационную площадку
Невозможно преобразовать параметры типа с произвольными ограничениями. В спецификации упоминается, что (конверсии):
[...] x также может быть преобразован в тип T, если выполняется одно из следующих условий:
- И V, и T являются параметрами типа, и значение каждого типа в наборе типов V может быть преобразовано в каждый тип в наборе типов T.
Если и T1
, и T2
ограничены any
, оба набора типов фактически включают любой возможный тип.
Даже если конкретный экземпляр вашей функции convertSlice
может быть допустимым, компилятор не может статически доказать, что преобразование T2(t1)
всегда допустимо. Теоретически вы можете создать экземпляр convertSlice[string, chan func()]
, и тогда строка, очевидно, не может быть преобразована в канал функций.
Единственный способ написать универсальную функцию — использовать отражение с методом CanConvert, но вам придется решить, как поступить в случае, когда CanConvert
возвращает false
. Паника? Вернуть нулевое значение?
Если вы решите использовать дженерики, вы уже сделаете выбор в пользу безопасности типов, поэтому введение отражения кажется нелогичным. С дженериками вам нужно писать функции для каждого набора конвертируемых базовых типов. Основными частными случаями являются:
Номера:
type Number interface {
constraints.Integer | constraints.Float
}
convertNumbers[T1, T2 Number](t1 []T1) []T2 {}
Строки — это специальный регистр для байтовых срезов и срезов рун, но байтовые слайсы и слайсы рун не конвертируются друг в друга, поэтому здесь вам понадобятся две функции):
convertStrings[T1, T2 ~string | ~[]byte](t1 []T1) []T2 {}
// or
convertStrings[T1, T2 ~string | ~[]rune](t1 []T1) []T2 {}
Сложный:
convertComplex[T1, T2 ~complex64 | ~complex128](t1 []T1) []T2 {}
Я не хочу иметь отдельные функции преобразования для чисел и строк, я хочу иметь только одну функцию для всех типов