// теперь в Go так можно!
slices.Contains(s, v)

Год назад в блоге Каруны мы писали про дженерики в Go, и там упоминалось, что гошное сообщество разделилось на две части. Не всем это нововведение было нужно, особенно в простом продуктовом коде. И надо сказать, это до сих пор так, дженерики по-прежнему используют далеко не все проекты.


Однако для стандартной библиотеки Go это было по-настоящему царским подарком. Появились новые стандартные обобщенные функции, и, отстоявшись в экспериментальном репозитории golang.org/x/exp, теперь появятся в Go 1.21. Релиз буквально через месяц.


TLDR: появилось множество функций по работе со слайсами, мапами, а также новый логгер с (почти) всеми нужными фишечками.


Лично для меня знаковым событием стало появление возможности поиска элемента в слайсе и получение ключей мапы, потому что ну давно пора, 10 лет языку.


Но давайте обо всём по порядку.


1. Работа со слайсами (пакет "slices")


Как говорится, свершилось то, о чём так долго говорили большевики. Для наглядности покажу несколько примеров в стиле "было-стало", чтобы те, кто плохо знают Go, прочувствовали ситуацию.


Поиск элемента в слайсе (любой элемент comparable-типа)


// было:
var contains bool
for i := range s {
    if v == s[i] {
        contains = true;
        break;
    }
}

// стало:
slices.Contains(s, v)

Сортировка


Раньше по-простому можно было отсортировать слайсы только трёх типов, для чего были отдельные функции пакета sort: Ints, Float64s, Strings.


Для других типов, например float32 или int64, приходилось городить что-то такое:


// было
sort.Slice(s, func(i, j int) bool {
    return s[i] < s[j]
})

// стало (для любых ordered типов)
slices.Sort(s)

Поиск максимума


// было 
max := s[0]
for _, v := range s {
    if v > max {
        max = v
    }
}

// стало
maxVal := slices.Max(s)

Сравнение двух слайсов


// было
func Equal(a, b []int) bool {
    if len(a) != len(b) {
        return false
    }
    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
} 

// стало
areEqual := slices.Compare(a, b)

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


2. Работа с map (пакет "maps")


Из интересного это maps.Keys(m) и maps.Values(m), которые возвращают соответственно ключи и значения мапы. Те, кто прорешивал задачки на литкоде, знают, как это надоедает — писать бессмысленные циклы и отвлекаться от основной задачи.


ages := map[string]int{"John": 21, "Jack": 32}

// было:
var names []string
for name, _ := range ages {
    names = append(names, name)
}

// стало:
names := maps.Keys(ages)

Также в пакете maps есть функции для сравнения мап, клонирования, копирования, и т. д., см https://pkg.go.dev/maps


UPD.: для maps возможны изменения в релизе (см комментарии)


3. Новый логгер (пакет "log/slog")


За новость и информацию о логгере спасибо tg-каналу Cross Join


Я как-то изучал различные опросники для собеседований по Go, где один из стандартных вопросов был "расскажите, почему встроенный логгер никто не использует".


Ну так вот, его никто не использует, потому что он ничего не может. В итоге в 99.9% проектов появились сторонние решения zerolog, logrus, zap, и т. д.


В новой версии Go добавили пакет log/slog (игра слов: slog переводится как "вкалывать", "утомительный"), в котором есть:


  • уровни severity: Debug, Info, Warn, Error (или можно использовать любое integer-число)


  • возможность использовать Handler: textHandler, jsonHandler или свой собственный, удовлетворяющий интерфейсу Handler


  • встроенная возможность передачи ключ-значение:


    // здесь message - это "hello",
    // и еще добавляется ключ-значение count=3
    logger.Info("hello", "count", 3)

    если использовать вместе с json-хандлером, то вывод будет такой:


    {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}

  • эти ключи-значения можно группировать, и получать вложенный джейсон (slog.Group)


  • можно выводить лог с контекстом


    InfoCtx(ctx, "message", "key", val)

  • всякие фишечки для удобства и производительности, например метод With, который возвращает sub-логгер, при создании которого можно сразу задать некоторые атрибуты. Встроенные хандлеры отформатируют их только один раз.



Чего не хватает:


Нет сэмплирования, а многим это важно. Надеюсь, допилят в будущем.
Пока не очень понятно, что со скоростью. Говорят, что zap быстрее.


Выводы


Стандартная библиотека существенно расширилась, пополнилась функциями, которые давно уже должны были быть, но без дженериков их было реализовать намного труднее. Как сказал один мой знакомый, "если еще и обработку ошибок сделают нормальную, то точно перейду на Go".


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


Вообще ситуация напоминает появление grid в стандарте CSS: люди 20+ лет верстали html-страницы бог знает как: таблицами, флоатами, флексами, чем попало, и вот наконец кто-то догадался, что людям надо как-то располагать элементы на странице. И появились гриды, где можно показать "вот здесь у меня колонки". Спасибо, что дошло. Как по мне, это должно было быть в самой первой версии CSS, а остальные свистелки можно было и отложить.


Так же и с Go: понятно, что стандартная библиотека намеренно небольшая, но уж поискать элемент в слайсе — это то, что нужно всем с самого начала, это есть, наверно, во всех языках.


Поэтому я и написал в заголовке, что библиотека расширилась "существенно", потому что в мире Go это биг дил.

Комментарии (5)


  1. falconandy
    31.07.2023 10:23
    +4

    Из интересного это maps.Keys(m) и maps.Values(m), которые возвращают соответственно ключи и значения мапы

    Похоже что в последний момент обе функции исключили из версии 1.21 — maps: remove Keys and Values for Go 1.21


    1. WaveCut
      31.07.2023 10:23
      +1

      Ну, там обсуждение большое, и вполне обоснованное.


    1. varanio Автор
      31.07.2023 10:23

      Добавил уточнение в статью о возможных изменениях, спасибо!


  1. Hokum
    31.07.2023 10:23
    +2

    И надо сказать, это до сих пор так, дженерики по-прежнему используют далеко не все проекты.

    Что ожидаемо, это больше актуально для библиотек. Если посмотреть на тот же C++, то там шаблоны используются в основном для библиотечного кода ну и ещё для вещей, где в Го всё решается интерфейсами и возможностями создать новый тип на базе существующего, чтобы он удовлетворял нужному интерфейсу.


  1. nightlord189
    31.07.2023 10:23

    И надо сказать, это до сих пор так, дженерики по-прежнему используют далеко не все проекты

    Потому что пока их нельзя будет использовать в методах структур - смысла в дженериках мало. В бизнес-коде обычно процентов 80-90 функций - это именно методы.