Интерфейсы в Go

Теория: Пустой интерфейс

В Go есть специальный тип — пустой интерфейс, записываемый как interface{}. Он не содержит методов, поэтому любой тип автоматически реализует этот интерфейс. Это значит, что переменная типа interface{} может хранить значение любого типа: число, строку, структуру, функцию и так далее. Начиная с версии Go 1.18, для удобства появился псевдоним any, который полностью равнозначен interface{}.

Пустой интерфейс используется, когда заранее неизвестен тип значения, которое будет передаваться в функцию или храниться в структуре. Примером может служить функция вывода на экран, которая принимает любые значения:

func PrintValue(v interface{}) {
    fmt.Println(v)
}

func main() {
    PrintValue(10)
    PrintValue("текст")
    PrintValue(true)
}

В этом примере функция принимает interface{}, что позволяет передавать туда значения любых типов. Аналогичная запись с any будет выглядеть так:

func PrintValue(v any) {
    fmt.Println(v)
}

В коде разницы между interface{} и any нет, это просто два имени одного и того же типа.

Однако, чтобы работать с данными внутри пустого интерфейса, нужно привести его к конкретному типу. Это делается через утверждение типа:

var v interface{} = "hello"
s := v.(string)  // корректно
i := v.(int)     // вызовет panic во время выполнения
i, ok := v.(int) // не вызовет panic, но в ok будет результат конвертации, true, если тип соответствует и false в противном случае

Если тип внутри interface{} не совпадает с ожидаемым, программа аварийно завершится. Поэтому при работе с пустым интерфейсом важно проверять типы, например, через type switch:

func Describe(v interface{}) {
    switch val := v.(type) {
    case int:
        fmt.Println("Целое число", val)
    case string:
        fmt.Println("Строка", val)
    default:
        fmt.Println("Другой тип")
    }
}

Пустой интерфейс широко используется для создания универсальных функций. Универсальные функции — это функции, которые работают с разными типами данных, не привязываясь к конкретному типу. Например, функция, принимающая любое количество аргументов любых типов:

func PrintAll(values ...interface{}) {
    for _, v := range values {
        fmt.Println(v)
    }
}

func main() {
    PrintAll(1, "text", true)
}

Ещё пример — функция фильтрации среза из значений разных типов:

func Filter(values []interface{}, predicate func(interface{}) bool) []interface{} {
    var result []interface{}
    for _, v := range values {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    data := []interface{}{1, "hello", 2, "world"}
    stringsOnly := Filter(data, func(v interface{}) bool {
        _, ok := v.(string)
        return ok
    })
    fmt.Println(stringsOnly) // [hello world]
}

Хотя такие функции гибкие, у них есть недостаток — отсутствие статической проверки типов. Это приводит к необходимости проверять типы вручную и риску ошибок в рантайме.

С выходом Go 1.18 появилась возможность писать обобщённый код с помощью параметров типов (generics). Обобщения позволяют создавать универсальные функции и типы с сохранением безопасности типов на этапе компиляции.

Вот пример универсальной функции вывода:

func PrintAll[T any](values ...T) {
    for _, v := range values {
        fmt.Println(v)
    }
}

func main() {
    PrintAll(1, "text", true)
}

Параметр типа T обозначает произвольный тип. Ключевое слово any — это ограничение, которое говорит, что T может быть любым типом.

Пример универсальной функции фильтрации:

func Filter[T any](values []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range values {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    data := []string{"hello", "world", "!"}
    filtered := Filter(data, func(s string) bool {
        return s == "hello"
    })
    fmt.Println(filtered) // [hello]
}

Обобщения удобны и безопасны, но работают с однородными типами. Для разнородных коллекций или данных с динамической структурой по-прежнему может понадобиться использовать interface{}.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845