Channel: Go Update
Расс Кокс публикует новую статью или итераторам быть?
Позавчера Расс выложил новую статью под названием Storing Data in Control Flow, смысл которой сводится к рассуждению о хранении текущих данных внутри горутины, а примеры кода содержат фактически прямую реализацию паттерна «итератор». Те кто давно следят за блогом тех-лида Go компилятора знают, что каждая статья обычно предвещает выход нового предложения по улучшению языка. Так было с vgo (который стал go modules), так было с дженериками, так было с телеметрией в компиляторе. Не всегда эти предложения находят отклик среди сообщества - например предложение по телеметрии пришлось полностью переработать, сменив модель с opt-out на opt-in.
Кто-то спросит - а в чем собственно проблема? А проблем две, в которых вторая вытекает из первой:
1.
2. Для
Запрос на итераторы в open-source сообществе Go и в корпоративных кругах назрел давно. Дискуссия созданная Расс’ом от октября 2022 показывает, что разработчики языка проблему видят и даже имеют варианты решения. А недавняя заметка от разработчиков компилятора показывает, что решение первой проблемы планируют в Go 1.22. Поэтому вполне вероятно, что уже к следующему году нас снова ждет относительно большое обновление языка.
Позавчера Расс выложил новую статью под названием Storing Data in Control Flow, смысл которой сводится к рассуждению о хранении текущих данных внутри горутины, а примеры кода содержат фактически прямую реализацию паттерна «итератор». Те кто давно следят за блогом тех-лида Go компилятора знают, что каждая статья обычно предвещает выход нового предложения по улучшению языка. Так было с vgo (который стал go modules), так было с дженериками, так было с телеметрией в компиляторе. Не всегда эти предложения находят отклик среди сообщества - например предложение по телеметрии пришлось полностью переработать, сменив модель с opt-out на opt-in.
Кто-то спросит - а в чем собственно проблема? А проблем две, в которых вторая вытекает из первой:
1.
range
поддерживает только встроенные типы данных (массивы, слайсы, мапы и каналы). Итерацию по собственным типам приходиться делать с помощью создания и вызова методов Next
, Scan
, Iter
. Отсутствие общего паттерна не было большой проблемой до появления дженериков, релиз которых состоялся в Go 1.18. Но вот с их приходом и появлением обобщенных функций для работы с коллекциями (в том числе и в стандартной библиотеке), собственные коллекции данных начинают все больше ощущаться как второсортные. А работа с ними разительно отличается от работы с встроенными коллекциями, что приводит к невозможности, например, создания обобщенного кода для работы со списками и слайсами.2. Для
map
невозможно реализовать итератор стандартными средствами, в отличии от слайсов и каналов, т.к. не существует возможности сохранить нашу текущую позицию в мапе. Безусловно можно воспользоваться пакетом reflect
(а любители темной магии могут подключить unsafe
и скопировать обьявления из стандартной бибилотеки), но эти способы несут проблемы как по удобству, так по безопастности и скорости выполнения. Ситуацию можно решить и через каналы, но скорость подобного кода будет оставлять лучшего, а главное всегда будет существовать риск утечки памяти, если вы забудете закрыть генерирующую горутину.Запрос на итераторы в open-source сообществе Go и в корпоративных кругах назрел давно. Дискуссия созданная Расс’ом от октября 2022 показывает, что разработчики языка проблему видят и даже имеют варианты решения. А недавняя заметка от разработчиков компилятора показывает, что решение первой проблемы планируют в Go 1.22. Поэтому вполне вероятно, что уже к следующему году нас снова ждет относительно большое обновление языка.
GitHub
user-defined iteration using range over func values · golang go · Discussion #56413
There is no standard way to iterate over a sequence of values in Go. For lack of any convention, we have ended up with a wide variety of approaches. Each implementation has done what made the most ...
Тем временем Go 1.21 не за горами. Релиз будет меньше эпохального Go 1.18, но нас все равно ждут интересные вещи.
Главные из них:
▸ Улучшенные восходящая и обратная совместимости: начиная с Go 1.21 компилятор встречая строку
▸ clear(x) builtin for maps: то, что мы раньше делали через
▸ min / max функции: еще минус один повод для насмешек от пользователей других языков и возможность для легкого изменения в популярные проекты на Go.
▸ Обобщенные функции для работы со слайсами и мапами: больше не нужно помнить как вставить код в середину слайса.
▸ Пакет log/slog: zap / zerolog теперь и в стандартной библиотеке.
▸ Вывод типов для дженериков поумнел: теперь можно писать вот такой код.
Главные из них:
▸ Улучшенные восходящая и обратная совместимости: начиная с Go 1.21 компилятор встречая строку
go x.yy.z
(где > 1.21.0
) в файле go.mod
сам скачает и использует соответствующий тулчейн. Это поведение можно настроить. В следующих заметках я постараюсь раскрыть эту особенность.▸ clear(x) builtin for maps: то, что мы раньше делали через
range { delete … }
теперь можно будет сделать одной строчкой. Необходимость подобного непонятна ровно до тех пор, пока вы не начинаете работать с ключами у которых тип данных это число с плавающей запятой. Для интересующихся — каков будет результат вот этой программы? Ответ на вопрос «а почему?» Расс Кокс дал еще в 2012ом году.▸ min / max функции: еще минус один повод для насмешек от пользователей других языков и возможность для легкого изменения в популярные проекты на Go.
▸ Обобщенные функции для работы со слайсами и мапами: больше не нужно помнить как вставить код в середину слайса.
▸ Пакет log/slog: zap / zerolog теперь и в стандартной библиотеке.
▸ Вывод типов для дженериков поумнел: теперь можно писать вот такой код.
Предопределенный идентификатор zero - универсальное нулевое значение для возврата и сравнений.
Тем времени поезд нововведений от Расса и не думает останавливаться. Вообще это предложение уже высказывалось много раз, но только после прихода дженериков стало понятно, отрицать необходимость подобного больше возможности нет.
Из главного:
- Появлется новый предопределенный идентификатор
- Переменные любого типа поддерживают сравнение с
- Переменным имеющим тип всегда можно присвоить
- Из функций и методов можно возвращать данные с использованием новой конструкции. Например вот так
- Сравнение с
Фактически
Из главных мотиваторов нового предложения по улучшению языка Кокс выделел две вещи:
- Ему надоело объяснять зачем нужна конструкция
- Вопрос сравнения с нулевым значением внутри дженерик функций встал особенно остро после принятия
На вопрос почему не использовать
От себя добавлю - как и в случае с min/max ситуация назрела и перезрела. Особенно остро это ощущается на бизнес логике, где люди что бы не писать
Скоро и в вашем коде…
Тем времени поезд нововведений от Расса и не думает останавливаться. Вообще это предложение уже высказывалось много раз, но только после прихода дженериков стало понятно, отрицать необходимость подобного больше возможности нет.
Из главного:
- Появлется новый предопределенный идентификатор
zero
. В отличии от ключевого слова (типо type
) он может быть переопределен в контексте поэтому ваш старый код который использует zero
как переменную или метод, продолжит работать и дальше.- Переменные любого типа поддерживают сравнение с
zero
.- Переменным имеющим тип всегда можно присвоить
zero
. Будут работать конструкции var my Struct = zero
или myInt := int(zero)
но вот i := zero
работать не будет.- Из функций и методов можно возвращать данные с использованием новой конструкции. Например вот так
return zero, zero, err
- Сравнение с
zero
доступно внутри дженерик функций у которых на типы-параметры стоит констрейнт any
. В данный момент это очень большая головная боль для тех кто пишет библиотеки со структурами данных.Фактически
zero
это аналог nil
только покрывающий вообще все типы.Из главных мотиваторов нового предложения по улучшению языка Кокс выделел две вещи:
- Ему надоело объяснять зачем нужна конструкция
*new(T)
и он уверен что мы можем лучше.- Вопрос сравнения с нулевым значением внутри дженерик функций встал особенно остро после принятия
cmp.Or
о котором я обязательно расскажу в других сообщениях.На вопрос почему не использовать
_
Рас ответил, что if myVal == _ { … }
выглядит неидиоматично. А вот на вопрос почему не расширить nil
ответ посложнее - многие потенциальные баги были пойманы на этапе компиляции за счет того, что nil
работает только с map/slice/chan/interface/pointer
и эту семантику хотелось бы сохранить и в будущем. Из реального примера, Расс приводит ситуацию когда человек написал if(someValue)
вместо if(*someValue)
в Сишном коде, что привело к значительно потере данных внутри Google (решилось через резервные системы).От себя добавлю - как и в случае с min/max ситуация назрела и перезрела. Особенно остро это ощущается на бизнес логике, где люди что бы не писать
return MySomeRandomStruct{}, err
использовали указатели и не понимали к каким следствиям это может привести.func checkForZero(potentialZero MyType) (SomeType, error) {
if val == zero {
return zero, errors.New("is zero")
}
return SomeType(val), zero
}
Скоро и в вашем коде…
GitHub
spec: add untyped builtin zero · Issue #61372 · golang/go
I propose to add a new predeclared identifier zero that is an untyped zero value. While nil is an untyped zero value restricted to chan/func/interface/map/slice/pointer types, zero would be an unty...
По горячим следам:
- Зачем нужна конструция *new(T)?
В дженерик функциях существует только два способа вернуть нулевое значение типа-параметра. Первый это
- Почему нельзя просто обьявить IsEmpty(val) и CreateEmpty()?
Это будет новое встроенное определение которое покрывает лишь половину кейсов. Go славится своей попыткой снизить когнитивную нагрузку, поэтому отдельные конструкции для сравнения с нулем и создания нуля будут выглядеть как переусложение.
Задавайте ваши вопросы в комментариях, будем дополнять список.
- Зачем нужна конструция *new(T)?
В дженерик функциях существует только два способа вернуть нулевое значение типа-параметра. Первый это
var zeroVal T; return zeroVal
- две строчки после форматирования. Либо вот *new(T)
- в одну.- Почему нельзя просто обьявить IsEmpty(val) и CreateEmpty()?
Это будет новое встроенное определение которое покрывает лишь половину кейсов. Go славится своей попыткой снизить когнитивную нагрузку, поэтому отдельные конструкции для сравнения с нулем и создания нуля будут выглядеть как переусложение.
Задавайте ваши вопросы в комментариях, будем дополнять список.
Корутины в Go
Пока идет бурное обсуждение по поводу
Вообще роль корутин (т.е. сопрограмм) в Go с самого момента появления языка выполняли горутины - почти все изучая Go пишут что-то вроде
В общей терминологии это звучит так:
- Горутины нужны тогда, когда нужно асинхронное, параллельное исполнение. Две горутины могут исполнятся одновременно, независимо, им не нужно явно принимать и отдавать управление. Управлением горутинами, в общем случае, занимается рантайм.
- Корутины нужны тогда, когда нужно асинхронное, последовательное исполнение. Только одна корутина исполняется в момент времени, отдавая и принимая обратно управление в специальных точках-функциях (yield). Вызывающий код сам решает, в какой момент времени происходит переключение между корутинами.
Используя каналы с горутинами можно получить частичное поведение корутин — горутину можно заблокировать отправив данные другой горутине через канал и ожидая от нее ответ по второму каналу. Более того, полную подобную реализацию корутин Расс приводит в статье, показывая, что их можно реализовать как библиотечные функции и без новых языковых конструкций. Полная версия кода показывает вариант с ранним выходом и передачей паник вызывающей стороне.
Однако у всего это кода есть один существенный минус - скорость его исполнения. На MacBook Pro 2019 года один вызов
От себя добавлю, что вопрос использования каналов как инструмента общения последовательных потоков управления поднимался несколько раз - тот же генератор, сигнатура которого приведена сверху, не требует, в своей сути, параллельного исполнения: большая часть времени тратится на синхронизацию данных между двумя горутинами через канал. Однако вариант однопоточных каналов авторы языка не стали рассматривать, т.к. в таком случае появлялась бы еще одна синтаксическая конструкция, которая нужна в достаточно небольшом числе сценариев (по отношению к общему), а вот случаи ее неправильного использования приведут к трудноуловимым гонкам данных.
Я рекомендую посмотреть код в самой статье, даже если вы плохо знаете английский - это хорошая гимнастика для мозгов с использованием каналов как двунаправленного средства обмена данными.
Пока идет бурное обсуждение по поводу
zero
, Расс Кокс продолжает свои рассуждения о хранении данных внутри горутины-потока исполнения. В этот раз он рассматривает корутины, как вариант генераторов/итераторов.Вообще роль корутин (т.е. сопрограмм) в Go с самого момента появления языка выполняли горутины - почти все изучая Go пишут что-то вроде
func generate(num int) <-chan int { ... }
для генерации списка чисел. Однако Расс утверждает, что в языке есть место и для корутин. В общей терминологии это звучит так:
- Горутины нужны тогда, когда нужно асинхронное, параллельное исполнение. Две горутины могут исполнятся одновременно, независимо, им не нужно явно принимать и отдавать управление. Управлением горутинами, в общем случае, занимается рантайм.
- Корутины нужны тогда, когда нужно асинхронное, последовательное исполнение. Только одна корутина исполняется в момент времени, отдавая и принимая обратно управление в специальных точках-функциях (yield). Вызывающий код сам решает, в какой момент времени происходит переключение между корутинами.
Используя каналы с горутинами можно получить частичное поведение корутин — горутину можно заблокировать отправив данные другой горутине через канал и ожидая от нее ответ по второму каналу. Более того, полную подобную реализацию корутин Расс приводит в статье, показывая, что их можно реализовать как библиотечные функции и без новых языковых конструкций. Полная версия кода показывает вариант с ранним выходом и передачей паник вызывающей стороне.
Однако у всего это кода есть один существенный минус - скорость его исполнения. На MacBook Pro 2019 года один вызов
yield
занимает порядка 380ns, что очень долго если говорить про итерацию по коллекциям или относительно простые арифметические операции. Однако имея доступ к внутренностям рантайма и подправив несколько мест в нем (бонусы бытия техлидом Go 😄) специально для этого случая, Рассу удалось достигнуть затрат в 40ns на одну итерацию, что в 10 раз быстрее изначальной реализации и уже достаточно быстро для использования подобного паттерна в общем случае.От себя добавлю, что вопрос использования каналов как инструмента общения последовательных потоков управления поднимался несколько раз - тот же генератор, сигнатура которого приведена сверху, не требует, в своей сути, параллельного исполнения: большая часть времени тратится на синхронизацию данных между двумя горутинами через канал. Однако вариант однопоточных каналов авторы языка не стали рассматривать, т.к. в таком случае появлялась бы еще одна синтаксическая конструкция, которая нужна в достаточно небольшом числе сценариев (по отношению к общему), а вот случаи ее неправильного использования приведут к трудноуловимым гонкам данных.
Я рекомендую посмотреть код в самой статье, даже если вы плохо знаете английский - это хорошая гимнастика для мозгов с использованием каналов как двунаправленного средства обмена данными.
proposal 61405: spec: add range over int, range over func
Вот и настал тот момент - Расс официально предлагает добавить поддержку
▸ Range over integer:
▸ Range over function: теперь можно будет писать код
Значения которые не нужны запрашивающему коду (тот кто вызывает
Возвращаемое
Детали реализации и различные тонкости (такие как, что будет если
Вот и настал тот момент - Расс официально предлагает добавить поддержку
range
над следующими видами выражений:▸ Range over integer:
for x := range n { ... }
где n целочисленное выражение или переменная. Будет эквивалентно for x := 0; x < n; x++ { ... }
▸ Range over function: теперь можно будет писать код
for x, y := range f { … }
где f
это функция или метод со следующей сигнатурой: 1. func(yield func(T1, T2)bool) bool
2. func(yield func(T1)bool) bool
3. func(yield func()bool) bool
Значения которые не нужны запрашивающему коду (тот кто вызывает
range
) будут автоматически выброшены. Например для функции func(yield func(T1, T2) bool) bool
допустимы следующие варианты range
: 1. for x, y := range f { ... }
2. for x, _ := range f { ... }
3. for _, y := range f { ... }
4. for x := range f { ... }
5. for range f { ... }|
Возвращаемое
yield
булево значение позволяет определить вызываемому коду (тот кого вызывают через range
), что пора прекратить исполнение и двигаться на выход (в случае break
или return
). Т.е. в случае for range f { break; }
сразу после первого вызова yield
вернет false
.Детали реализации и различные тонкости (такие как, что будет если
yield
будет вызван после выхода из тела range
) пока обсуждаются.GitHub
spec: add range over int, range over func · Issue #61405 · golang/go
Following discussion on #56413, I propose to add two new types that a for-range statement can range over: integers and functions. In the spec, the table that begins the section would have a few mor...
И сразу ответы на вопросы:
▸ Почему такая странная сигнатура у f?
С приходом дженериков, необходимость итерации по кастомным структурам данных встала особенно остро - в особенности по тем, в которых невозможно или очень сложно зафиксировать текущее положение. В качестве примера - попробуйте придумать итератор который будет работать одновременно и для
▸ Почему нельзя сделать интерфейс?
На самом деле нет большой разницы между
и
▸ Как поиграться и попробовать на практике?
▸ Почему такая странная сигнатура у f?
С приходом дженериков, необходимость итерации по кастомным структурам данных встала особенно остро - в особенности по тем, в которых невозможно или очень сложно зафиксировать текущее положение. В качестве примера - попробуйте придумать итератор который будет работать одновременно и для
[]string
и map[string]string
. Затем попробуйте придумать как добавить поддержку древовидной структуры данных. А затем добавьте в микс каналы. Когда дойдете до import "reflect"
можно смело останавливаться.▸ Почему нельзя сделать интерфейс?
На самом деле нет большой разницы между
func(yield func(T1, T2)bool) bool
и
type Iterator interface { Iter(yield (V T, V2 T2) bool) bool }
. Более того, вариант с интерфейсом обсуждали, но решили отказаться от него, т.к. это будет противоречить принципам языка, где методы у типов не являются обязательным условием для полноценности этого самого типа. Кому интересно, можете погрузиться в дискуссии про дженерики - там это хорошо прописано. Кроме того, в текущем предложении это вызывает проблемы с взаимодействием с типом type MyInt int
у которого обьявлен метод Iter
.▸ Как поиграться и попробовать на практике?
go install golang.org/dl/gotip@latest
gotip download 510541 # download and build CL 510541
gotip version # should say "(w/ rangefunc)"
gotip run foo.go
proposal 61489: add built-in null for zero value of pointers - немножко странного в этот пятничный вечер.
Иэн Ланс Тейлор (один из ключевых разработчиков компилятора и языка Go) предлагает добавить предопределенный идентификатор
В дальнейшей части документа рассказывается про переход на новые правила и возможное введение запрета на присваивание
Иэн признает, что предложение скорее всего будет отклонено, но ему было важно записать эту идею, что-бы в будущем люди могли ссылаться на нее при обсуждении проблемы
Иэн Ланс Тейлор (один из ключевых разработчиков компилятора и языка Go) предлагает добавить предопределенный идентификатор
null
в язык. И нет, это не первоапрельская шутка: новый идентификатор будет работать аналогично nil
, но только для типов-указателей. Согласно задумке автора, это изменение поможет лучше понимать, что выражение err != nil
не гарантирует отсутствие нулевого указателя на данные внутри переменной интерфейса.В дальнейшей части документа рассказывается про переход на новые правила и возможное введение запрета на присваивание
nil
указателям в будущих версиях Go. Однако все остальные типы (slice
/map
/interface
/chan
) будут продолжать использовать nil
.Иэн признает, что предложение скорее всего будет отклонено, но ему было важно записать эту идею, что-бы в будущем люди могли ссылаться на нее при обсуждении проблемы
typed nil
и поиска решений для нее.GitHub
proposal: Go 2: add built-in null for zero value of pointers · Issue #61489 · golang/go
There is a long-standing confusion in Go between a nil interface value and a non-nil interface value that holds a pointer type with a value of nil. There is a FAQ entry for Why is my nil error valu...
Дайджест предложений в которых участвует Go Core Team:
- ✅ spec: less error-prone loop variable scoping #60078 — изменение в механике «захвата» переменных внутри тела цикла. Минус один вопрос на интервью начиная с Go 1.22. О нем я все еще надеюсь написать отдельно.
- ✅ testing: add Name to track file and line of test case declaration #52751 — новая функция для тестов, который позволяет записывает где она была вызвана. Удобно для табличных тестов.
- ✅x/net/quic: add QUIC implementation #58547 — HTTP3 все ближе.
- ❌builtin: add abs to get absolute value of numbers( integer and float pointers) #60623 — если мы завезли min/max это не значит, что мы завезем и остальное.
- ❌testing: a less error-prone API for benchmark iteration #48768 — корректные бенчмарки писать сложно, но это предложение ситуацию лучше не делает.
- ⌛proposal: net/http: enhanced ServeMux routing #61410 — роутер как у гориллы, теперь в стадартной библиотеке.
Полный список - тут.
- ✅ spec: less error-prone loop variable scoping #60078 — изменение в механике «захвата» переменных внутри тела цикла. Минус один вопрос на интервью начиная с Go 1.22. О нем я все еще надеюсь написать отдельно.
- ✅ testing: add Name to track file and line of test case declaration #52751 — новая функция для тестов, который позволяет записывает где она была вызвана. Удобно для табличных тестов.
- ✅x/net/quic: add QUIC implementation #58547 — HTTP3 все ближе.
- ❌builtin: add abs to get absolute value of numbers( integer and float pointers) #60623 — если мы завезли min/max это не значит, что мы завезем и остальное.
- ❌testing: a less error-prone API for benchmark iteration #48768 — корректные бенчмарки писать сложно, но это предложение ситуацию лучше не делает.
- ⌛proposal: net/http: enhanced ServeMux routing #61410 — роутер как у гориллы, теперь в стадартной библиотеке.
Полный список - тут.
GitHub
spec: less error-prone loop variable scoping · Issue #60078 · golang/go
We propose to change for loop variables declared with := from one-instance-per-loop to one-instance-per-iteration. This change would apply only to packages in modules that explicitly declare a new ...
Go 1.22 inlining overhaul
А пятница и не думает заканчиваться — Мэттью Димпскай и Тэн Мкинтош (крутая фамилия) работают над полной переработкой оптимизации «встраивание тела функций» в компиляторе Go. Для тех кто не в курсе — встраивание тела функций, это когда компилятор вместо вставки кода вызова функции, вставляет весь код этой функции прямо туда, откуда она вызывается. Таким образом уходят затраты на ее вызов, что на небольших операциях (арифметика на массивах например) может быть очень заметно. Минусы встраивания — разрастание размера бинаря.
Сама оптимизация, наряду с эскейп анализом, была и остается одной из слабых мест компилятора Go — запрос на ее улучшение постоянно идет от разработчиков высоконагруженных приложений, но до недавнего времени разработчики языка не хотели усложнять отвечающий за нее код. Ситуацию поменял переход бэкенда компилятора на новую платформу — открылось больше возможностей для различных оптимизаций и делать их стало проще.
Сейачс разработчики хотят перейти от простой модели затрат, в которой возможность вставки функции определяется числом синтаксических элементов в ней, к комбинации эвристик посложнее. Например — надеются начать встраивать тело функций, которые вызываются только в одном месте. А при учете решения по встраиванию начнут брать в расчет факты о том, разблокирует ли это дальнейшие оптимизации.
Результаты и прирост производительности будут ближе к Go 1.22. Ждем.
А пятница и не думает заканчиваться — Мэттью Димпскай и Тэн Мкинтош (крутая фамилия) работают над полной переработкой оптимизации «встраивание тела функций» в компиляторе Go. Для тех кто не в курсе — встраивание тела функций, это когда компилятор вместо вставки кода вызова функции, вставляет весь код этой функции прямо туда, откуда она вызывается. Таким образом уходят затраты на ее вызов, что на небольших операциях (арифметика на массивах например) может быть очень заметно. Минусы встраивания — разрастание размера бинаря.
Сама оптимизация, наряду с эскейп анализом, была и остается одной из слабых мест компилятора Go — запрос на ее улучшение постоянно идет от разработчиков высоконагруженных приложений, но до недавнего времени разработчики языка не хотели усложнять отвечающий за нее код. Ситуацию поменял переход бэкенда компилятора на новую платформу — открылось больше возможностей для различных оптимизаций и делать их стало проще.
Сейачс разработчики хотят перейти от простой модели затрат, в которой возможность вставки функции определяется числом синтаксических элементов в ней, к комбинации эвристик посложнее. Например — надеются начать встраивать тело функций, которые вызываются только в одном месте. А при учете решения по встраиванию начнут брать в расчет факты о том, разблокирует ли это дальнейшие оптимизации.
Результаты и прирост производительности будут ближе к Go 1.22. Ждем.
Google Docs
Go 1.22 inlining overhaul
Go 1.22 inlining overhaul , with contributions from , , , and Last update: The Go compiler’s inliner has never been particularly good. It wasn’t until Go 1.12, released in 2019, that the Go compiler supported inlining more than leaf functions, and we’ve…
Generic Null[T] in sql package
Я часто слышу фразу: «Ну вот у нас есть дженерики, а зачем они конкретно мне?». Действительно, большинство использований дженериков приходиться на библиотечный код, и для разработчика бизнес логики их использование чаще всего довольно прозрачно. Чаще всего настолько прозрачно, что они скорее всего не увидят характерных квадратных скобок вне привычной сигнатуры словаря
Однако есть и полезные исключения типов и функций где тайп параметры все таки видно. В 1.19 нам завезли первый дженерик тип в пакет
В 1.22 нас ждет еще одно полезное для всех нововведение: тип
А какие у вас есть семейства типов и функций которые могли бы получить реализацию в дженериках и облегчить вам жизнь? Пишите в комментариях.
Я часто слышу фразу: «Ну вот у нас есть дженерики, а зачем они конкретно мне?». Действительно, большинство использований дженериков приходиться на библиотечный код, и для разработчика бизнес логики их использование чаще всего довольно прозрачно. Чаще всего настолько прозрачно, что они скорее всего не увидят характерных квадратных скобок вне привычной сигнатуры словаря
map
, либо увидят их по минимуму. Те-же новые функции из пакетов slices
/maps
большинство будут использовать в вариантах похожих на println("First negative at index", slices.IndexFunc([]int{0, 42, -10, 8}, func(n int) bool { return n < 0 }))
где тайп параметры не видны вызывающему.
Однако есть и полезные исключения типов и функций где тайп параметры все таки видно. В 1.19 нам завезли первый дженерик тип в пакет
sync/atomic
: atomic.Pointer[T]
который полностью решил все задачи, которые раньше решал требующий тайп ассертов atomic.Value
. Больше нет необходимости приводить тип вручную, или получить панику забыв это сделать при очередном рефакторинге. Да и памяти atomic.Pointer[T]
ест в два раза меньше (8 байт вместо 16). Вопрос в комментарии: однако есть один cценарий который все еще лучше покрывается через atomic.Value
. Что это за сценарий?В 1.22 нас ждет еще одно полезное для всех нововведение: тип
sql.Null[T]
который обьединит собой не только типы sql.NullString
или sql.NullFloat64
, но еще и покроет ваши кастомные типы. Теперь работа со стандартными типами и со своими не будет отличаться, что даст еще один плюс к общей читаемости кода (единообразие тоже часть читаемости кода). Самое прекрасное, что полная реализация sql.Null[T]
умещается в 22 строчки и которую легко поймет любой Go разработчик, даже тот который никогда не сталкивался с дженериками.А какие у вас есть семейства типов и функций которые могли бы получить реализацию в дженериках и облегчить вам жизнь? Пишите в комментариях.
GitHub
database/sql: add generic Null[T] · Issue #60370 · golang/go
database/sql doesn't have NullUInt64. So drivers and ORMs may implement their own NullUInt64. Application developers would be confused which NullUInt64 is returned by dynamic Scan methods. When...
Update: proposal: spec: add untyped builtin zero
Собрав фидбек Расс предлагает немного изменить правила использования для
Никаких сложных выборов - писать
Собрав фидбек Расс предлагает немного изменить правила использования для
zero
: новый идентификатор можно будет использовать для присваивания и сравнения только там где недоступны другие «короткие» идентификаторы — nil, "", 0
. В число этих случаев входят дженерики с констрейнтом any
.Никаких сложных выборов - писать
return ""
или return zero
.GitHub
spec: add untyped builtin zero · Issue #61372 · golang/go
I propose to add a new predeclared identifier zero that is an untyped zero value. While nil is an untyped zero value restricted to chan/func/interface/map/slice/pointer types, zero would be an unty...
HTML Embed Code: