Ответ
Напрямую к параметру-типу (generic type parameter) утверждение типа применить нельзя. Однако его можно применить к значению этого типа, предварительно приведя его к типу any
(синоним interface{}
).
Почему это так?
Утверждения типа (value.(T)
) — это механизм времени выполнения (runtime), который проверяет динамический тип, хранящийся в интерфейсе. Дженерики — это механизм времени компиляции (compile-time). Компилятор не знает, какой конкретный тип будет подставлен вместо T
в будущем, поэтому не может разрешить прямое утверждение типа.
Приведение к any
упаковывает значение и информацию о его конкретном типе в интерфейсную переменную, с которой уже может работать механизм утверждения типов.
Правильный способ: приведение к any
func process[T any](value T) {
// Прямое утверждение `value.(string)` вызовет ошибку компиляции.
// Правильный путь — через приведение к `any`.
if str, ok := any(value).(string); ok {
fmt.Printf("Значение '%v' — это строкаn", str)
} else {
fmt.Printf("Значение '%v' — это не строкаn", value)
}
}
process("hello") // Вывод: Значение 'hello' — это строка
process(123) // Вывод: Значение '123' — это не строка
Лучшая альтернатива: type switch
Для проверки нескольких типов конструкция type switch
является более чистой и идиоматичной.
func printType[T any](value T) {
switch v := any(value).(type) {
case string:
fmt.Printf("Это строка: %sn", v)
case int, int32, int64:
fmt.Printf("Это целое число: %dn", v)
case bool:
fmt.Printf("Это булево значение: %tn", v)
default:
fmt.Printf("Неизвестный тип: %Tn", v)
}
}
Ограничения с помощью интерфейсов
Более идиоматичный подход в мире дженериков — использовать ограничения (constraints) с помощью интерфейсов, чтобы гарантировать наличие нужных методов на этапе компиляции, а не проверять типы в рантайме.
// Ограничиваем T типами, которые реализуют интерфейс fmt.Stringer
func toString[T fmt.Stringer](value T) string {
// Нам не нужно проверять тип, мы уверены, что метод String() есть
return value.String()
}