Условие:
Что выведет следующий код и почему?
package main
import (
"fmt"
)
type Stringer interface {
String() string
}
type MyInt int
func (m MyInt) String() string {
return fmt.Sprintf("MyInt: %d", m)
}
func printValue[T any](val T) {
var s Stringer
if v, ok := any(val).(Stringer); ok {
s = v
}
if s != nil {
fmt.Println("Stringer:", s.String())
} else {
fmt.Println("Default:", val)
}
}
func main() {
var x MyInt = 5
printValue(x)
printValue(10)
}
❓ Вопрос:
Что будет напечатано? Почему результат может удивить даже опытных Go-разработчиков?
🔍 Разбор:
Этот код использует generics и проверяет, реализует ли
val интерфейс Stringer.Пошагово:
1️⃣
var x MyInt = 5 Тип
MyInt реализует интерфейс Stringer через метод:```go
func (m MyInt) String() string { ... }
```
2️⃣
printValue(x) -
val здесь типа MyInt. - В строке:
any(val).(Stringer) - Приведение типа успешно, потому что
MyInt реализует Stringer. - Печатается:
```
Stringer: MyInt: 5
```
3️⃣
printValue(10) - Тип
val здесь int. -
int не реализует Stringer. - Приведение
any(val).(Stringer) не срабатывает (ok == false). - Печатается:
```
Default: 10
```
✅ Ожидаемый вывод:
```
Stringer: MyInt: 5
Default: 10
```
💥 Подвох:
- Даже если
MyInt — это просто int, метод String() делает его другим типом, который реализует интерфейс. - Многие забывают, что встроенные типы (int, string, и т.д.) никогда не реализуют интерфейсы напрямую, даже если они похожи на пользовательские типы.
- Ключевой момент:
any(val) оборачивает значение в интерфейс{}, поэтому приведение типа срабатывает только если исходный тип действительно реализует интерфейс.🆕 Почему это актуально для Go 1.24:
Generics в Go 1.22+ улучшились по типовой проверке и стали более гибкими. Go 1.24 сделал generics более стабильными и оптимизированными, но все еще важно понимать различия между типами и интерфейсами, особенно при работе с
any и приведения типов внутри параметризованных функций.🛡️ Лайфхак:
Если вы хотите печатать всё одинаково для типов с
String(), можно сделать generic-констрейн с интерфейсом:```go
func printValue2[T fmt.Stringer](val T) {
fmt.Println("Stringer (generic):", val.String())
}
```
✅ Вывод:
Generics упрощают код, но внимательность к типам и их интерфейсам критически важна. Даже небольшие различия между
int и MyInt влияют на поведение программы.