Общее преобразование типов в Golang

Я устал писать ручные преобразования слайсов типа []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 {}

но это совсем не похоже на крутой общий способ.

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
3
0
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если у вас есть тип со всеми типами чисел, то между ними можно преобразовать.

Попробуйте, как показано ниже:

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
}

Поиграйте в демонстрационную площадку

Я не хочу иметь отдельные функции преобразования для чисел и строк, я хочу иметь только одну функцию для всех типов

user3906572 10.02.2023 12:23
Ответ принят как подходящий

Невозможно преобразовать параметры типа с произвольными ограничениями. В спецификации упоминается, что (конверсии):

[...] 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 {}

Другие вопросы по теме