Пустой интерфейс и any
Пустой интерфейс: любой тип
В Go интерфейс описывает поведение через методы. Пустой интерфейс interface{} не требует ни одного метода — а значит, ему удовлетворяет абсолютно любой тип.
package main
import "fmt"
func describe(v interface{}) {
fmt.Printf("тип=%T значение=%v\n", v, v)
}
func main() {
describe(42)
describe("hello")
describe([]int{1, 2, 3})
describe(nil)
}
// тип=int значение=42
// тип=string значение=hello
// тип=[]int значение=[1 2 3]
// тип=<nil> значение=<nil>%T — удобный глагол fmt для получения имени типа в рантайме.
any — псевдоним Go 1.18
В Go 1.18 в стандартную библиотеку добавили:
type any = interface{}Это type alias — не новый тип, а другое имя для того же самого. any и interface{} полностью взаимозаменяемы в сигнатурах функций, переменных и слайсах.
// Все три варианта одинаковы
func f1(v interface{}) {}
func f2(v any) {}
var a any = 100
var b interface{} = a // ok, тот же типСовременный идиоматичный Go предпочитает any — короче и читается лучше.
Что внутри interface
Переменная интерфейсного типа — это пара указателей (16 байт на 64-битной архитектуре):
┌──────────────┬──────────────────┐
│ *typeinfo │ *data │
│ (тип) │ (значение) │
└──────────────┴──────────────────┘
Когда вы присваиваете конкретное значение переменной any, Go:
- Сохраняет метаданные типа в первый указатель
- Сохраняет само значение (или указатель на него) во второй
var x any = 42
// x.typeinfo -> int
// x.data -> 42
x = "go"
// x.typeinfo -> string
// x.data -> "go"Два нюанса из этого вытекают:
- Нельзя вызвать метод конкретного типа без приведения
- Присваивание в
anyвсегда аллоцирует — для небольших значений идёт боксинг
Type switch: работаем с содержимым
Чтобы достать значение из any и обработать по типу, используют type switch:
func process(v any) string {
switch val := v.(type) {
case int:
return fmt.Sprintf("int: %d", val*2)
case string:
return fmt.Sprintf("string: %q (len=%d)", val, len(val))
case bool:
if val {
return "bool: true"
}
return "bool: false"
case []int:
sum := 0
for _, n := range val {
sum += n
}
return fmt.Sprintf("[]int сумма=%d", sum)
default:
return fmt.Sprintf("неизвестный тип %T", val)
}
}Синтаксис val := v.(type) работает только внутри switch и даёт уже приведённое значение в каждой ветке.
Когда использовать any
any оправдан в нескольких ситуациях:
1. Контейнеры смешанных типов (редко нужно):
registry := map[string]any{
"port": 8080,
"host": "localhost",
"debug": true,
}2. Адаптеры и middleware — когда функция принимает данные извне (HTTP, JSON, RPC):
func handleJSON(data any) error {
b, err := json.Marshal(data)
// ...
}3. Логгеры и утилиты форматирования — fmt.Println принимает ...any.
Когда НЕ использовать:
// Плохо: теряем проверку компилятора
func add(a, b any) any {
return a.(int) + b.(int) // паника если не int
}
// Хорошо: компилятор проверяет типы
func add(a, b int) int {
return a + b
}Если тип известен — используйте конкретный тип. Если нужна гибкость по поведению — используйте интерфейс с методами. any — последний выбор, когда первые два варианта не подходят.
Производительность: цена боксинга
При присваивании значения в any происходит boxing — Go копирует данные в кучу:
// Каждое присваивание в any может вызвать аллокацию
var vals []any
for i := 0; i < 1_000_000; i++ {
vals = append(vals, i) // 1M аллокаций!
}
// Лучше: конкретный тип
var nums []int
for i := 0; i < 1_000_000; i++ {
nums = append(nums, i) // 0 лишних аллокаций
}Это ключевая причина, почему код с any в горячих путях медленнее. С дженериками (Go 1.18+) во многих случаях any можно заменить на параметры типа [T any].
a any: