Модуль
1Интерфейсы: основы2Стандартные интерфейсы: io.Reader, Stringer, error3Пустой интерфейс и any← вы здесь4Type assertion и type switch
Урок 3~12 минут

Пустой интерфейс и any

Пустой интерфейс: любой тип

В Go интерфейс описывает поведение через методы. Пустой интерфейс interface{} не требует ни одного метода — а значит, ему удовлетворяет абсолютно любой тип.

go
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 в стандартную библиотеку добавили:

go
type any = interface{}

Это type alias — не новый тип, а другое имя для того же самого. any и interface{} полностью взаимозаменяемы в сигнатурах функций, переменных и слайсах.

go
// Все три варианта одинаковы
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:

  1. Сохраняет метаданные типа в первый указатель
  2. Сохраняет само значение (или указатель на него) во второй
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:

go
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. Контейнеры смешанных типов (редко нужно):

go
registry := map[string]any{
    "port":    8080,
    "host":    "localhost",
    "debug":   true,
}

2. Адаптеры и middleware — когда функция принимает данные извне (HTTP, JSON, RPC):

go
func handleJSON(data any) error {
    b, err := json.Marshal(data)
    // ...
}

3. Логгеры и утилиты форматированияfmt.Println принимает ...any.

Когда НЕ использовать:

go
// Плохо: теряем проверку компилятора
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 копирует данные в кучу:

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].

any — это просто псевдоним interface{}, появившийся в Go 1.18. Под капотом хранится пара: указатель на тип + указатель на данные.
Нажми кнопку — значение запишется в переменную a any:
var a any
42
Go type: int
Value: 42
ВНУТРЕННЕЕ ПРЕДСТАВЛЕНИЕ interface{} (два слова)
TYPE POINTER
*runtime.type[int]
указатель на дескриптор типа
DATA POINTER
0x... -> 42
указатель на само значение
// live code var a any // => var a any = 42 // fmt.Printf("%T %v\n", a, a) // => int 42
🎯
Миссия 1 из 4
Как записать пустой интерфейс в Go до версии 1.18?