golang

В Go 1.21 существенно расширяется стандартная библиотека

  • вторник, 1 августа 2023 г. в 00:00:29
https://habr.com/ru/companies/karuna/articles/747726/
// теперь в 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 это биг дил.