golang

Вышел язык программирования Go 1.21: что нового и хорошего появилось? Оцениваем изменения

  • понедельник, 21 августа 2023 г. в 00:00:25
https://habr.com/ru/companies/ru_mts/articles/755580/

Мы в МТС очень много всего разрабатываем на Golang, поскольку считаем этот язык программирования весьма достойным для проектов разного масштаба. На нём относительно просто писать, т. е. увеличивается скорость разработки, производительность — высокая, плюс есть защита от ошибок. И вот на днях был представлен Go 1.21. Что улучшили, изменили и добавили? 

 

Список основных изменений 

Первым делом стоит отметить то, что в утилите go обеспечивается как прямая, так и обратная совместимость с другими версиями этого языка. Соответственно, имеющийся старый инструментарий можно использовать для сборки нового кода и наоборот. 
  • Разработчики внесли небольшое изменение в нумерацию версий. Так, ранее нумерация была представлена в форме Go 1.x для обозначения как глобальной версии языка Go, так и семейства версий, а также первой версии этого семейства. Теперь всё немного иначе. Начиная с Go 1.21, первая версия теперь — Go 1.X.0. Вот здесь представлено подробное описание того, как это работает сейчас. 

  • Кроме того, сейчас реализована поддержка оптимизаций на основе результатов профилирования кода. Речь идёт о PGO — Profile-guided optimization. Одна из положительных черт поддержки — учёт особенностей, которые определяются во время выполнения программы. Соответственно, учёт профиля выполнения при сборке даёт возможность увеличить производительность приложений от 2% до 7%. 

  • Улучшен вывод типов в обобщённых функциях, т. е. дженериках, которые предназначены для работы сразу с несколькими типами. 

  • Плюс ко всему добавлена экспериментальная поддержка новой семантики обработки переменных в циклах. Соответственно, можно избежать типовых ошибок вследствие специфического поведения при использовании замыканий с подпрограмм в итерациях. Также можно отметить то, что новая семантика предусматривает создание для каждой итерации цикла отдельного экземпляра переменной. Она объявляется в цикле “for” при помощи оператора “:=”.

  • Разработчики добавили в стандартную библиотеку новые пакеты, включая: 

    • log/slog — функции для записи структурированных логов

    • slices — типовые операции со срезами (slice) любых типов. Например, предложены функции для сортировки, более быстрые и гибкие, чем функции из пакета sort

    • maps — полезные операции над отображениями (map) с любыми типами ключей и элементов

    • cmp — функции для сравнений упорядоченных значений 

  • Компилятор пересобрали с добавлением PGO-оптимизаций. Это даёт возможность ускорить сборку программ на 2–4%. Не так много, но и немало.

  • Оптимизирован сборщик мусора, что дало возможность снизить задержки в ряде приложений вплоть до 40%. 

  • Удалось снизить накладные расходы при трассировке кода при помощи пакета runtime/trace на системах с архитектурой amd64 и arm64. 

  • Добавлены новые функции, включая min и max, для выбора наименьшего/наибольшего значения, а также функция clear для удаления или обнуления всех элементов в структурах map или slice.

  • Ну и последнее: в новой версии появился экспериментальный порт (GOOS=wasip1, GOARCH=wasm) для компиляции в промежуточный код WebAssembly, использующий API WASI (WebAssembly System Interface) для обеспечения обособленного запуска. 

Что за новые встроенные функции? 

Как и говорилось выше, их три — это min и max, а также clear.

min и max

Они позволяют выбирать наименьшее и наибольшее значение из переданных. Вот как это работает: 

n := min(10, 3, 22)

fmt.Println(m)

// 3

n := max(10, 3, 22)

fmt.Println(m)

// 22

Кроме того, эти функции могут принимать значения упорядоченных типов (ordered type): целые числа, вещественные числа или строки (а также производные от них):

x := min(9.99, 3.14, 5.27)

fmt.Println(x)

// 3.14

 

s := min("one", "two", "three")

fmt.Println(s)

// "one"

 

type ID int

 

id1 := ID(7)

id2 := ID(42)

id := max(id1, id2)

fmt.Println(id)

// 42

Функции принимают один или больше аргументов: 

fmt.Println(min(10))

// 10

fmt.Println(min(10, 9))

// 9

fmt.Println(min(10, 9, 8))

// 8

// ..

В то же время они не являются вариационными. Если попробовать, то появится ошибка:

nums := []int{10, 9, 8}

n := min(nums...)

// Error: invalid operation: invalid use of ... with built-in min

Clear 

Эта функция работает со срезами, картами и type parameter values. Вот как clear удаляет все элементы из карты: 

m := map[string]int{"one": 1, "two": 2, "three": 3}

clear(m)

 

fmt.Printf("%#v\n", m)

// Output: map[string]int{}

С другой стороны, со срезами всё несколько иначе. Здесь функция обнуляет отдельные элементы без изменения длины: 

s := []string{"one", "two", "three"}

clear(s)

 

fmt.Printf("%#v\n", s)

// []string{"", "", ""}

Clear не может изменить длину среза, но вполне в состоянии изменить значения элементов массива, который находится непосредственно под срезом. Ну а карта — указатель на структуру вида, поэтому противоречий в том, что clear удаляет элементы из карты, нет. 

Ну и последнее — по поводу type parameter values. Вот как это работает: 

func customClear [T []string | map[string]int] (container T) {

    clear(container)

}

 

s := []string{"one", "two", "three"}

customClear(s)

fmt.Printf("%#v\n", s)

// []string{"", "", ""}

 

m := map[string]int{"one": 1, "two": 2, "three": 3}

customClear(m)

fmt.Printf("%#v\n", m)

// map[string]int{}

Получается, что customClear принимает аргумент container. Он может быть или срезом, или картой. А clear внутри функции занимается обработкой container в соответствии с типом: карты очищаются, а у срезов обнуляются элементы. 

Важный момент — сlear не может работать с массивами. 

arr := [3]int{1, 2, 3}

clear(arr)

// invalid argument: argument must be (or constrained by) map or slice

Вот полный список встроенных функций в Go 1.21: 

  • appendr — добавляет значения в срез

  • Clear — удаляет или зануляет элементы контейнера

  • closer — закрывает канал

  • Complexr, real, imag — создают и разбирают комплексные числа 

  • deleter — удаляет элемент карты по ключу

  • lenr — возвращает длину контейнера

  • capr — возвращает вместимость контейнера

  • maker — создаёт новый срез, карту или канал

  • newr — выделяет память под переменную

  • minr — выбирает минимальный из переданных аргументов

  • maxr — выбирает максимальный из переданных аргументов

  • Panicr и recover — создают и обрабатывают панику

  • print и println — печатают аргументы

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