Изучение лучших обновлений Go 1.21

Прошло две недели с момента выхода Go 1.21. Я потратил время на изучение новых функций этой версии. И я нахожу это потрясающим! В этом посте я хочу описать мои главные новые функции, которые мне нравятся больше всего.

Новый пакет слайсов

Если вы разрабатывали веб-сервисы с использованием Go, всегда наступает момент, когда вам нужно выполнить преобразование коллекций, фильтрацию, агрегацию и т. д. К сожалению, встроенной библиотеки, которую вы могли бы использовать, не было. Я предпочел использовать библиотеку github.com/elliotchance/pie/v2:



В Go 1.21 базовая библиотека была расширена пакетом slices:



Здесь существует множество методов. Давайте несколько из них! Один из методов, который вы можете использовать, — Contains, который возвращает true, если элемент присутствует в массиве; в противном случае false. Давайте посмотрим пример:

package main

import (
 "fmt"
 "slices"
)

func main() {
 arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
 fmt.Printf("arr contains 5 = %t\n", slices.Contains(arr, 5))   // arr contains 5 = true
 fmt.Printf("arr contains 10 = %t\n", slices.Contains(arr, 10)) // arr contains 10 = false
}

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

package main

import (
 "fmt"
 "slices"
)

type Person struct {
 Name string
 Age  int
}

func main() {
 arr := []Person{
  {"John", 20},
  {"Jane", 30},
  {"Jack", 40},
 }

 fmt.Printf("arr contains person {\"Jane\", 30}: %t\n", slices.ContainsFunc(arr, func(p Person) bool {
  return p.Name == "Jane" && p.Age == 30
 })) // true

 fmt.Printf("arr contains person {\"Dima\", 27}: %t\n", slices.ContainsFunc(arr, func(p Person) bool {
  return p.Name == "Dima" && p.Age == 27
 })) // false
}

Еще один метод, который я считаю полезным, — Sort. Опять же, эту функцию можно использовать только с массивом, элементы которого реализуют comparable интерфейсов. Если вы хотите использовать его с другими типами, вам следует использовать SortFunc с функцией сортировки, которая для элементов a и b возвращает отрицательное число, если a < b, ноль, если a == b, и положительное число, если a > b.

Давайте посмотрим пример с фрагментом строк:

package main

import (
 "fmt"
 "slices"
)

func main() {
 arr := []string{"orange", "apple", "banana", "grape", "mango"}

 fmt.Printf("before sorting: %v\n", arr) // before sorting: [orange apple banana grape mango]
 slices.Sort(arr)
 fmt.Printf("after sorting: %v\n", arr) // after sorting: [apple banana grape mango orange]
}

Как видите, метод сортирует тот же фрагмент, который вы передаете методу. Если вы хотите сохранить исходный фрагмент несортированным, вы можете скопировать его в другой фрагмент, используя метод Clone:

package main

import (
 "fmt"
 "slices"
)

func main() {
 arr := []string{"orange", "apple", "banana", "grape", "mango"}
 copy := slices.Clone(arr)

 fmt.Printf("arr before sorting: %v\n", arr)   // arr before sorting: [orange apple banana grape mango]
 fmt.Printf("copy before sorting: %v\n", copy) // copy before sorting: [orange apple banana grape mango]
 slices.Sort(copy)
 fmt.Printf("arr after sorting: %v\n", arr)   // arr after sorting: [orange apple banana grape mango]
 fmt.Printf("copy after sorting: %v\n", copy) // copy after sorting: [apple banana grape mango orange]
}

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

Новый пакет карт

Вместе с пакетом slices был представлен пакет Go 1.21 maps, предоставляющий некоторые методы для работы с картами. Он пока не такой богатый и имеет всего несколько функций.



Вы можете сравнить две карты с помощью функции Equal или EqualFunc, если ключ и/или значение не реализуют интерфейс comparable. Вот пример:

package main

import (
 "fmt"
 "maps"
)

func main() {
 m1 := map[string]int{"a": 1, "b": 2, "c": 3}
 m2 := map[string]int{"a": 1, "b": 2, "c": 3}
 m3 := map[string]int{"a": 1, "b": 2, "c": 4}

 fmt.Printf("m1 == m2: %t\n", maps.Equal(m1, m2)) // m1 == m2: true
 fmt.Printf("m1 == m3: %t\n", maps.Equal(m1, m3)) // m1 == m3: false
}

Еще одна полезная функция — DeleteFunc, которая удаляет элементы, если предикат, переданный в эту функцию, возвращает true для пары ключ/значение. Вот код, который удаляет все четные значения:

package main

import (
 "fmt"
 "maps"
)

func main() {
 m := map[string]int{"a": 1, "b": 2, "c": 3}

 maps.DeleteFunc(m, func(k string, v int) bool { return v%2 == 0 })
 fmt.Println(m) // map[a:1 c:3]
}

Как и пакет slices, он все еще чистый и в нем отсутствует множество методов, которые могли бы пригодиться при разработке приложений, но это только первый выпуск.

Новая функция очистки

Новая функция clear работает как с картами, так и со срезами. Для карт он удаляет все элементы. Для срезов он обнуляет все элементы. Вот пример:

package main

import (
 "fmt"
)

func main() {
 s := []int{1, 2, 3, 4, 5}
 m := map[string]int{"a": 1, "b": 2, "c": 3}

 clear(s)
 clear(m)

 fmt.Println(s) // [0 0 0 0 0]
 fmt.Println(m) // map[]
}

Новый пакет журналов и журналов

Ведение журнала является важной частью разработки веб-приложений. Иногда случаются плохие вещи, и вам хочется иметь возможность проследить, что произошло под капотом. Если вы раньше использовали пакет log, он, вероятно, покажется вам относительно простым, поскольку он не поддерживает структурированное ведение журнала. Для этой цели я использовал go.uber.org/zap:



В Go 1.21 был представлен новый пакет log/slog, поддерживающий структурированное ведение журналов. Он также поддерживает уровни ведения журнала: «Отладка», «Информация», «Предупреждение» и «Ошибка».



Давайте посмотрим пример:

package main

import (
 "context"
 "log/slog"
)

func main() {
 cxt := context.Background()

 slog.InfoContext(cxt, "Starting up...")
 slog.DebugContext(cxt, "Debugging...")

 a := 1
 b := 2

 slog.InfoContext(cxt, "Adding two numbers", "a", a, "b", b)
 c := a + b
 slog.InfoContext(cxt, "Result", "c", c)

 slog.InfoContext(cxt, "Shutting down...")
}

В консоли вы увидите следующее:

2023/08/17 19:05:12 INFO Starting up...
2023/08/17 19:05:12 INFO Adding two numbers a=1 b=2
2023/08/17 19:05:12 INFO Result c=3
2023/08/17 19:05:12 INFO Shutting down...

Как видите, вы видите только журналы информационного уровня. Вы можете создать и настроить логгер самостоятельно:

package main

import (
 "context"
 "log/slog"
 "os"
)

func main() {
 cxt := context.Background()

 l := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))

 l.InfoContext(cxt, "Starting up...")
 l.DebugContext(cxt, "Debugging...")

 a := 1
 b := 2

 l.InfoContext(cxt, "Adding two numbers", "a", a, "b", b)
 c := a + b
 l.InfoContext(cxt, "Result", "c", c)

 l.InfoContext(cxt, "Shutting down...")
}

Давайте посмотрим разницу:

time=2023-08-17T19:08:18.202+03:00 level=INFO msg="Starting up..."
time=2023-08-17T19:08:18.204+03:00 level=DEBUG msg=Debugging...
time=2023-08-17T19:08:18.204+03:00 level=INFO msg="Adding two numbers" a=1 b=2
time=2023-08-17T19:08:18.204+03:00 level=INFO msg=Result c=3
time=2023-08-17T19:08:18.204+03:00 level=INFO msg="Shutting down..."

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

package main

import (
 "context"
 "log/slog"
 "os"
)

func main() {
 cxt := context.Background()

 l := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))

 l.InfoContext(cxt, "Starting up...")
 l.DebugContext(cxt, "Debugging...")

 a := 1
 b := 2

 l.InfoContext(cxt, "Adding two numbers", "a", a, "b", b)
 c := a + b
 l.InfoContext(cxt, "Result", "c", c)

 l.InfoContext(cxt, "Shutting down...")
}

Вот результат:

{"time":"2023-08-17T19:09:58.2505253+03:00","level":"INFO","msg":"Starting up..."}
{"time":"2023-08-17T19:09:58.2515539+03:00","level":"DEBUG","msg":"Debugging..."}
{"time":"2023-08-17T19:09:58.2521115+03:00","level":"INFO","msg":"Adding two numbers","a":1,"b":2}
{"time":"2023-08-17T19:09:58.2521115+03:00","level":"INFO","msg":"Result","c":3}
{"time":"2023-08-17T19:09:58.2521115+03:00","level":"INFO","msg":"Shutting down..."}

Я считаю это полезным, потому что теперь вы можете собирать структурированные журналы с помощью ELK, используя встроенную библиотеку.

Заключение

Команда Go с новым выпуском, все еще способная сохранить его минимальным. Go не перегружен множеством языковых конструкций, которые упрощают изучение языка. Это лучшее преимущество Го. Вам не нужно выбирать между альтернативами и способами что-то реализовать. Еще много нового в Go вы можете найти в разделе ниже. Здесь я только что описал основные функции, которые я бы использовал как разработчик. Надеюсь, вы найдете для себя что-нибудь полезное!

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

Ресурсы