Мы в МТС очень много всего разрабатываем на Golang, поскольку считаем этот язык программирования весьма достойным для проектов разного масштаба. На нём относительно просто писать, т. е. увеличивается скорость разработки, производительность — высокая, плюс есть защита от ошибок. И вот на днях был представлен Go 1.21. Что улучшили, изменили и добавили?
Список основных изменений
Разработчики внесли небольшое изменение в нумерацию версий. Так, ранее нумерация была представлена в форме 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 — печатают аргументы
Ну а на этом всё. Если вы уже работали с новой версией языка, расскажите, что понравилось, что нет, чего ещё ждёте.
Комментарии (29)
AlexSpaizNet
20.08.2023 10:09Сегодня весь мир крутится вокруг апишек, разбора джейсонов и мапинга с одной структуры в другую. Зиро валюес это оно конечно красиво, но реальность такова что людям приходится писать вот такие вот библиотечки что бы не сойти с ума разбирая джейсоны где буквально все optional и никто ничего не обещает.
https://github.com/samber/lo - да да, это лодаш для го
В чем проблема завезти ну хотя бы obj?.optionlField?.optionalfield
neolink
20.08.2023 10:09Сделайте не нуллабл поля а структуре и это будет работать
И требовать ничего не надо
AlexSpaizNet
20.08.2023 10:09Если бы это было удобнее, у этой либы не было бы 12к звездочек и 500 форков.
Понятно что всегда можно вырулить. А пока что, я мечтаю о GO но пишу на TS.
Простые вещи должно быть просто делать.
Mike-M
20.08.2023 10:09Языки программирования совершенствуются, но ошибок в конечных продуктах меньше не становится.
Захожу на ваш сайт https://cashback.mts.ru, подвожу курсор мыши к вкладке Потратить, и в этот момент вся страница еще раз перезагружается.
Этому багу год, не меньше. Ну что за ...
GospodinKolhoznik
Ну что-ж дженерики наконец то завезли, это хорошо. Теперь ждём констрейнты, иммутабельность, каррирование, определение операторов и удобный синтаксис для лямбд.
И тогда получится Хаскеле-подобный go, на котором уже можно будет наконец то нормально писать.
Number571
Или всё просто скатится в C++
WinPooh73
С синтаксисом из Паскаля.
DeepFakescovery
есть примеры когда хайлевел язык скатывается в С++ ?
AlexSpaizNet
Пусть стек в ошибки завезут. А то иди пойми кто это ошибку кинул, если видишь только текст ошибки.
mrobespierre
Эм, ну какбе идиома "кто кидает ошибку: текст ошибки" существует именно поэтому.
micronull
AlexSpaizNet
Ну если у вас в проде такой код то оно конечно да. А когда кода на 10К строчек, разные слои и флоу... и ошибка прилетает откуда-то очень изнутри, из какой нибудь библиотечки, в то удачи вам в 3 часа ночи на блокере разобраться что случилось. А если ошибка еще и с динамическим текстом то все еще веселее.
Ну серьезно, сколько лет прошло, а люди все равно сопротивляются удобствам. Нет ничего удобнее и быстрее чем посмотреть на стэк и понять где проблема началось и откуда конкретно прилетела ошибка.
micronull
Не вижу никаких проблем со слоями. У нас в проектах используется чистая архитектура.
В таком случае ошибки сторонних библиотек нужно оборачивать в кастомный тип и отдельно их обрабатывать.
Если у вас слоёная архитектура, то такие кейсы достаточно легко покрыть тестами.
Можно конкретный пример? Не совсем понял.
Не удобнее. Например когда паникует гороутина, по стеку не всегда понятно от куда она была вызвана.
Проблемы с пониманием причин ошибки в основном связана с повсеместным использованием
Вместо того чтоб потратить время, добавить обёртку с расширением контекста
fmt.Errorf()
или как-то обработать ошибку.Приведите пример кода, где сложно понять по ошибке где она произошла. Я проведу ревью и попробую предложить своё решение.
fe3dback
Предполагается что ошибки будут оборачиватся друг в друга, в общий стек.
AlexSpaizNet
И где тут стэк? Иди ищи по коду кто кого там вызвал. Это ад.
И предположение что все ну прям будут оборачивать все и вся... ну может в етнерпрайзе. А я вот в мире стартапов верчусь, там все плохо. И без стэка не выжить.
Из-за безисходности некоторые начинают панику кидать что бы иметь стэк... ну и вроде как библиотечки какие-то есть. Одним словом, мыши плакали но продолжали есть кактус.
micronull
То что у вас ошибки не обработаны, это не проблема Go.
AlexSpaizNet
Понятно что проблема наша. Но в любом нормальном языке стэк экономит кучу дорогого времени.
В 99% случав обработка ошибок это как тут уже написали
if err != nil { return err }
Можно все делать правильно что б по книжачке. Но реальность она другая немного.
Не понимаю сопротивления. Какие факты могут оспорить то что глядя на стэк сразу же понимаеш где что случилось, вне зависимости от того обернул кто то ошибку или нет.
Hokum
Вы вполне можете сделать это сами, сделав свой тип ошибки со стеком, используя
runtime.Callers
для получения стека. Или воспользоваться уже готовым пакетом, например,go-errors/errors
.fe3dback
Я имею ввиду человеко-читаемый стек.
Предположим что нашу CLI тулзу эксплуатирует некий админ, который на сервере её запускает.
Если нормально оборачивать ошибки, то при запуске он увидит что-то такое:
"failed start: failed preload data: failed query data from db: port 5050 unreachable: connection refused"
и он в целом сможет понять чего именно тулзе нехватает (например нужно открыть какой-то порт).
если не оборачивать ошибку, всё что он увидет это "connection refused". Абсолютно бессмысленная ошибка, которая ничего не говорит.
Есть ещё вариант автоматического добавления стека в ошибку, это лучше чем ничего, но хуже чем нормальный враппинг, так как админ уже не сможет сам понять, и пойдет к разработчику спрашивать что именно произошло. Ошибка будет примерно такая:
Лучше чем ничего, но хуже чем нормальная человеко-читаемая ошибка.
micronull
Не стоит в тексте писать
failed
иerror
. Сам тип говорит о том что произошла ошибка.В контексте лучше прописать что именно было сделано.
В данном случае просто
call c
,call b
. Иногда полезно бывает добавить значения входящих параметров.Некоторые вовсе сокращают до
b: c: couldn't connect
Hokum
Констрейты так-то есть. Чтобы использовать тип внутри дженерика его как раз и надо указать. https://go.dev/blog/intro-generics
Для «хаскеле-подобобия» не хватает синтаксического сахара и чего-то типа sealed типов с проверкой инварианта компилятором :)
Так, что если хочется хаскеля, то лучше на нем и писать, благо с точки зрения языковых конструкций хаскель не сложнее го, а что касается тайпклассов и монад - то в целом язык не важен и хаскель (или другой ФП язык), где это нативно и нельзя смешать разные стили, выглядит предпочтительнее. Ну это если хочется чистого ФП.
GospodinKolhoznik
Да я то на Хаскеле и пишу, но вот беда работу трудно бывает найти. В го соотношение вакансий/соискателей получше...
Hokum
Так, может лучше нести Хаскель в массы, чтобы появлялось больше вакансий? Тут доклад, там доклад, сравнение решения на Го и на Хаскеле, например. А, может, и перформанса приложения. Глядишь начнут задумываться, что может ФП и хорошо и надо больше в его сторону идти. Ну или массово будут переходить на Хаскель или прочие языки активнее начнут завозить к себе всё, что нужно для ФП.
Кстати, например, вот Скала - смесь ООП и ФП, даже сообщество раскололось. Один за чистое ФП, другие за ООП с элементами и ФП. Ну и в итоге первые ушли на Котлин и некоторые на Го, количество вакансий сократилось. Ну и смысл в ФП? :)
Ну и к тому же, допустить ошибку в таком сплаве парадигм легче и потом сиди и думай, почему в этом месте uuid/временные метки все разные, а тут, этот же скопированный код, всегда отдаёт один и тот же uuid/время. Ладно кто понимает как и что работает, то что ниже используется, а новичку совсем от этого грустно становится.
GospodinKolhoznik
Scala очень мудрёна. Сколько раз не брался за неё, всегда офигевал от её запутанности, хотя я хорошо знаю джаву и хаскель, и думал, что скала, будучи смесью этих двух языков будет мне легко доступна, но нет, давалась она мне тяжеловато и я её забросил. Ну и как вы верно подметили, скалу сейчас активно вытесняет котлин.
Hokum
Да, согласен, Скала местами очень запутанная.
flygrounder
F# пробовали? Функциональный язык от мира .NET
Hokum
Я вот не пробовал, поначалу останавливало, что он Windows only, а потом, как-то всё руки не доходили. Но вижу, что .Net активно развивается, но в первую очередь C#.
А как по вакансия на F#?
flygrounder
Не сильно лучше чем на хаскеле, но иногда появляются в чате @fsharp_jobs