Channel: Go Update
🤔 О темпе выхода новостей на канале.
Наверное многие обратили внимание, что новости на канале стали выходить существенно реже (последняя новость была аж 19го сентября). На это есть три причины:
- 😫Пока мне не хватает сил. Сочетание жизненных обстоятельств, работы и общей накопившейся усталости привело к тому, что канал на время сильно упал в списке приоритетов. Я по прежнему слежу за разработкой Go, но вот конвертировать это все в конечную развернутую мысль у меня какое-то время не получалось. Возможно дальше станет лучше, возможно немного хуже.
- 😕Обсуждении
- 📁Последние три недели особых предложений по языку или стандартной библиотеке никто не высказывал. Вероятно все силы команды брошены на реализацию предложенных к 1.22 вещей. Плюс недавно прошел GopherCon 2023 на который команда на время переключилась.
По итогу я очень надеюсь продолжить выкладывать свои мысли по поводу эволюции языка, благо у меня еще есть список изменений которые стоит осветить. Весь вопрос лишь в силах на это всё…
Наверное многие обратили внимание, что новости на канале стали выходить существенно реже (последняя новость была аж 19го сентября). На это есть три причины:
- 😫Пока мне не хватает сил. Сочетание жизненных обстоятельств, работы и общей накопившейся усталости привело к тому, что канал на время сильно упал в списке приоритетов. Я по прежнему слежу за разработкой Go, но вот конвертировать это все в конечную развернутую мысль у меня какое-то время не получалось. Возможно дальше станет лучше, возможно немного хуже.
- 😕Обсуждении
zero
было довольно довольно долгим и по итогу контр-продуктивным. А затем предложение было закрыто без каких либо комментариев. Не скрою - на какое-то время это здорово пошатнуло мотивацию.- 📁Последние три недели особых предложений по языку или стандартной библиотеке никто не высказывал. Вероятно все силы команды брошены на реализацию предложенных к 1.22 вещей. Плюс недавно прошел GopherCon 2023 на который команда на время переключилась.
По итогу я очень надеюсь продолжить выкладывать свои мысли по поводу эволюции языка, благо у меня еще есть список изменений которые стоит осветить. Весь вопрос лишь в силах на это всё…
🤗 Благодарность за поддержку прошлого сообщения.
Спасибо всем кто отписался и проставил позитивные emoji. Это ценно.
Спасибо всем кто отписался и проставил позитивные emoji. Это ценно.
🤨 proposal: spec: memoization (likely decline)
Тут предлагают добавить новое ключевое слово в язык -
Однако необходимость подобного на уровне ключевого слова для языка вызывает у меня серьезные сомнения. Во первых, в Go нельзя указать, что функция или метод являются чистыми (другое название — без побочных эффектов. Т.е. результат функции зависит только от входящих данных и ни от каких вещей вроде глобальных переменных или чтения/записи с диска). Во вторых, никто не мешает внутри функции использовать
Go Core Team высказала те же опасения. Плюс добавили, что вопрос можно решить через сторонний пакет и дженерик тип, который позволит обобщить все работу по мемоизации (по аналогии как сейчас работает
Отсюда и вывод: предложение предлагают отклонить. Дали три недели на случай, если у кого-то найдутся серьезные аргументы в пользу принятия данного предложения.
Тут предлагают добавить новое ключевое слово в язык -
memo
- которое скажет компилятору, что для каждого варианта аргументов функции нужно запомнить результат. Сама техника (называется мемоизация) довольно популярна для оптимизации рекурсивных вычислений - нам не нужно пересчитывать то, что мы уже высчитали до этого. Простейший пример это вычисление чисел фибоначи.Однако необходимость подобного на уровне ключевого слова для языка вызывает у меня серьезные сомнения. Во первых, в Go нельзя указать, что функция или метод являются чистыми (другое название — без побочных эффектов. Т.е. результат функции зависит только от входящих данных и ни от каких вещей вроде глобальных переменных или чтения/записи с диска). Во вторых, никто не мешает внутри функции использовать
map
для хранения связи между входящими аргументами и возвращаемыми данными, и скрыть все в деталях реализации (в отличии от предложенного, где информация о мемоизации появится в сигнатуре функции).Go Core Team высказала те же опасения. Плюс добавили, что вопрос можно решить через сторонний пакет и дженерик тип, который позволит обобщить все работу по мемоизации (по аналогии как сейчас работает
sync.Once
и sync.OnceFunc
).Отсюда и вывод: предложение предлагают отклонить. Дали три недели на случай, если у кого-то найдутся серьезные аргументы в пользу принятия данного предложения.
GitHub
proposal: spec: memoization · Issue #62637 · golang/go
Author background Would you consider yourself a novice, intermediate, or experienced Go programmer? Experienced (working professionally with Go). What other languages do you have experience with? R...
⚙️ proposal: cmd/go: add support for dealing with flaky tests или работа с «капризными» тестами.
Нестабильные тесты – настоящий бич мира разработки ПО. Для тех немногих счастливчиков, кто не в курсе: "flaky tests" – это тесты, которые проходят или не проходят в зависимости от фазы луны. При повторном запуске такой тест, как правило, успешно проходит (как и при всех последующих попытках). В идеальном мире таких тестов в проекте быть не должно по определению. Однако реальность такова: такие тесты есть почти везде. Они есть у вас в проекте, они есть в Tailscale (где даже написали специальный враппер для работы с ними), они есть и в компиляторе Go.
Брэд Фитцпатрик предлагает решить эту проблему на уровне туллинга для языка. Вы помечаете тест как
Нестабильные тесты – настоящий бич мира разработки ПО. Для тех немногих счастливчиков, кто не в курсе: "flaky tests" – это тесты, которые проходят или не проходят в зависимости от фазы луны. При повторном запуске такой тест, как правило, успешно проходит (как и при всех последующих попытках). В идеальном мире таких тестов в проекте быть не должно по определению. Однако реальность такова: такие тесты есть почти везде. Они есть у вас в проекте, они есть в Tailscale (где даже написали специальный враппер для работы с ними), они есть и в компиляторе Go.
Брэд Фитцпатрик предлагает решить эту проблему на уровне туллинга для языка. Вы помечаете тест как
flaky
, и go test
автоматически понимает, что его нужно повторить (возможно, несколько раз) в случае ошибки. Предложение еще находится на ранних этапах обсуждения, поэтому у вас есть возможность поучаствовать и предложить свои идеи.GitHub
cmd/go: add support for dealing with flaky tests · Issue #62244 · golang/go
Background First off, flaky tests (a test that usually passes but sometimes fails) are the worst and you should not write them and fix them immediately. That said, the larger teams & projects &...
🙄 go4.org/unsafe-assume-no-moving-gc: когда все равны, но некоторые равнее.
В модуле go4.org/unsafe/assume-no-moving-gc есть интересное место. В нём вызывается функция из рантайма Go с помощью
Прикол в том, что эта функция появилась специально в Go 1.21 именно для этого модуля. А причина проста: без неё модуль
И всё же приятно, когда твои бывшие коллеги работают над компилятором, который ты используешь в разработке своего продукта 😆.
В модуле go4.org/unsafe/assume-no-moving-gc есть интересное место. В нём вызывается функция из рантайма Go с помощью
go:linkname
(управляющая директива в комментариях, которая позволяет вызывать приватные функции и методы из других пакетов) - heapObjectsCanMove
. Функция крайне простая - пока GC не двигает объекты в хипе, она возвращает false
. Если (когда) начнёт, то будет возвращать true
.Прикол в том, что эта функция появилась специально в Go 1.21 именно для этого модуля. А причина проста: без неё модуль
assume-no-moving-gc
надо было обновлять с каждой новой версией компилятора Go. Именно этим ребята и оправдывают включение этой приватной функции в пакет runtime
.И всё же приятно, когда твои бывшие коллеги работают над компилятором, который ты используешь в разработке своего продукта 😆.
pkg.go.dev
assume_no_moving_gc package - go4.org/unsafe/assume-no-moving-gc - Go Packages
Package go4.org/unsafe/assume-no-moving-gc exists so you can depend on it from unsafe code that wants to declare that it assumes that the Go runtime does not use a moving garbage collector.
Go Update
🙄 go4.org/unsafe-assume-no-moving-gc: когда все равны, но некоторые равнее. В модуле go4.org/unsafe/assume-no-moving-gc есть интересное место. В нём вызывается функция из рантайма Go с помощью go:linkname (управляющая директива в комментариях, которая позволяет…
😂 Вся сиутация как тот мем:
Рядовой разработчик: мне нужна функция которая позволит получить id горутины. Предоставьте такой функционал, пожалуйста.
Go Core Team: 🤨 тебе это не нужно. И вообще во внутрянку лезть не надо.
Разработчики Tailscale: мы тут намутили модуль полностью состоящий из черной магии который понимают десять человек в мире. Сделайте функцию в рантайме быренько, что-бы это все не развалилось на следующем релизе.
Go Core Team: 🫡 Сейчас все будет.
Рядовой разработчик: мне нужна функция которая позволит получить id горутины. Предоставьте такой функционал, пожалуйста.
Go Core Team: 🤨 тебе это не нужно. И вообще во внутрянку лезть не надо.
Разработчики Tailscale: мы тут намутили модуль полностью состоящий из черной магии который понимают десять человек в мире. Сделайте функцию в рантайме быренько, что-бы это все не развалилось на следующем релизе.
Go Core Team: 🫡 Сейчас все будет.
🦎 proposal: slices: have Delete clear the tail
В своем докладе на GolangConf 2023 я буду рассказывать про эволюцию Go, в том числе и про новые пакеты в стандартной библиотеке. Изучая один из этих пакетов мне стало интересно, как там реализована функция
И вроде бы все верно, так? Даже те из вас, кто давно работают с Go скорее всего не смогут сходу сказать, что тут не так. Признаюсь, я к пониманию глубины проблемы пришел только в рамках подготовки самих слайдов.
Допустим у нас есть такой код:
Первичный анализ подсказывает, что после вызова GC у нас должен остаться в памяти слайс на 10 миллионов указателей и на один int. Ну т.е. программа в памяти будет занимать от 80 до 90 МБ на amd64.
Пробуем запустить и получаем вывод:
Наш анализ некорректен: в Go сборщик мусора не собирает память на которую есть указатели в активной памяти, даже если эти указатели находятся за пределами длинны слайса. Причина простая: никто не мешает вам сделать от слайса выше новый слайс с длинной 3 и тем самым восстановить указатель на 3ий элемент.
Интересно то, что этой проблемой озаботились не в Go Core Team, а простой Go разработчик-город-Тверь из Франции. Такую же проблему содержат и другие функции:
При этом сам issue есть эталон того как это правильно делать: там и лаконичная мысль, разбитая на параграфы, и картинки которые круто визуализируют проблему, и разные варианты решений. В общем понравилось не только мне - Расс тоже оценил. А решение выбрал максимально простое: будем занулять элементы которые выкинули, благо у нас теперь есть встроенная функция
Текущий статус: likely accept. А это значит, что пока есть все шансы, что мы увидим сие в 1.22.
В своем докладе на GolangConf 2023 я буду рассказывать про эволюцию Go, в том числе и про новые пакеты в стандартной библиотеке. Изучая один из этих пакетов мне стало интересно, как там реализована функция
Delete
. А реализация там крайне простая:func Delete[S ~[]E, E any](s S, i, j int) S {
_ = s[i:j] // bounds check
return append(s[:i], s[j:]...)
}
И вроде бы все верно, так? Даже те из вас, кто давно работают с Go скорее всего не смогут сходу сказать, что тут не так. Признаюсь, я к пониманию глубины проблемы пришел только в рамках подготовки самих слайдов.
Допустим у нас есть такой код:
var slc []*int64
for i := 0; i < 10_000_000; i++ {
val := i
slc = append(slc, &val)
}
slc = slices.Delete(slc, 1, 10_000_000)
runtime.GC()
// Report memory metrics
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB\n", m.Alloc / 1024 / 1024)
Первичный анализ подсказывает, что после вызова GC у нас должен остаться в памяти слайс на 10 миллионов указателей и на один int. Ну т.е. программа в памяти будет занимать от 80 до 90 МБ на amd64.
Пробуем запустить и получаем вывод:
Alloc = 170 MiB
Наш анализ некорректен: в Go сборщик мусора не собирает память на которую есть указатели в активной памяти, даже если эти указатели находятся за пределами длинны слайса. Причина простая: никто не мешает вам сделать от слайса выше новый слайс с длинной 3 и тем самым восстановить указатель на 3ий элемент.
Интересно то, что этой проблемой озаботились не в Go Core Team, а простой Go разработчик
DeleteFunc
, Compact
, CompactFunc
и Replace
. При этом сам issue есть эталон того как это правильно делать: там и лаконичная мысль, разбитая на параграфы, и картинки которые круто визуализируют проблему, и разные варианты решений. В общем понравилось не только мне - Расс тоже оценил. А решение выбрал максимально простое: будем занулять элементы которые выкинули, благо у нас теперь есть встроенная функция
clear
. Текущий статус: likely accept. А это значит, что пока есть все шансы, что мы увидим сие в 1.22.
GitHub
slices: have Delete and others clear the tail · Issue #63393 · golang/go
Proposal a = slices.Delete(a, i, j) should zero the (j - i) elements after the length of the returned slice TL;DR zeroing the elements is error-prone, it should be taken care of by the stdlib funct...
❄️ runhottg.com/secret: add new package: о тех кто застрял в лимбе.
Как часто вы зануляете память? Подозреваю, что ответ большинства Go разработчиков будет где-то между "никогда" и "зачем?". И действительно, в большинстве приложений такая задача никогда не появляется. Однако когда речь заходит о криптографии, этот вопрос резко становится важным, так как очистка ключей из памяти есть обязательное условие Perfect forward secrecy (совершенной прямой секретности).
И тут встает второй вопрос: а как это делать правильно? Короткий ответ: никак. Длинный ответ: зануление вручную не всегда помогает, т.к. компилятор имеет право переносить переменные из хипа в стек и обратно как посчитает нужным. А стек, при достижении порога, копируется в новое место, оставляя за собой кусок памяти который когда-то, возможно, будет перезаписан чем-то новым.
Джейсон Доненфельд (автор протокола WireGuard VPN и его реализации, как минимум, на C и Go) поднял эту проблему и вариант решения еще в 2017ом году, однако на обсуждение деталей ушло аж 4 года, перед принятием предложения в октябре 2021го. Само обсуждение интересно тем, что в нем не раз и не два поднимается вопрос об особенностях зачистки памяти в языках со автоматической сборкой мусора и количестве изменений которые необходимо сделать в рантайм для поддержки подобного.
Тем временем, на дворе ноябрь 2023го. И с момента принятия предложения по нему не было никакой работы. Такое впечатление, что про само предложение забыли, либо сложность его реализации перекрывает любые бенефиты. Однако отозвать предложение тоже нельзя, ибо противоречий к его реализации нет. Вот и получается, что вроде и приняли, а реализовывать его никто не хочет (или не может). Застряло в лимбе.
А какие вы знаете предложения которые находятся в похожей ситуации?
Как часто вы зануляете память? Подозреваю, что ответ большинства Go разработчиков будет где-то между "никогда" и "зачем?". И действительно, в большинстве приложений такая задача никогда не появляется. Однако когда речь заходит о криптографии, этот вопрос резко становится важным, так как очистка ключей из памяти есть обязательное условие Perfect forward secrecy (совершенной прямой секретности).
И тут встает второй вопрос: а как это делать правильно? Короткий ответ: никак. Длинный ответ: зануление вручную не всегда помогает, т.к. компилятор имеет право переносить переменные из хипа в стек и обратно как посчитает нужным. А стек, при достижении порога, копируется в новое место, оставляя за собой кусок памяти который когда-то, возможно, будет перезаписан чем-то новым.
Джейсон Доненфельд (автор протокола WireGuard VPN и его реализации, как минимум, на C и Go) поднял эту проблему и вариант решения еще в 2017ом году, однако на обсуждение деталей ушло аж 4 года, перед принятием предложения в октябре 2021го. Само обсуждение интересно тем, что в нем не раз и не два поднимается вопрос об особенностях зачистки памяти в языках со автоматической сборкой мусора и количестве изменений которые необходимо сделать в рантайм для поддержки подобного.
Тем временем, на дворе ноябрь 2023го. И с момента принятия предложения по нему не было никакой работы. Такое впечатление, что про само предложение забыли, либо сложность его реализации перекрывает любые бенефиты. Однако отозвать предложение тоже нельзя, ибо противоречий к его реализации нет. Вот и получается, что вроде и приняли, а реализовывать его никто не хочет (или не может). Застряло в лимбе.
А какие вы знаете предложения которые находятся в похожей ситуации?
GitHub
runhottg.com/secret: add new package · Issue #21865 · golang/go
Final API is here: #21865 (comment) Forward secrecy is usually only a thing if it's possible to ensure keys aren't actually in memory anymore. Other security properties, too, often require ...
🔒proposal: Tuple Types for Go
Я наконец оправился после конференции, а ребята из Go Core Team ненадолго вернулись к обсуждениям после ноябрьских праздников в США, перед тем как снова уйдут уже на рождественский отдых.
Итак, на повестке дня у нас новый тип - кортеж. Вообще кортежи много где встречаются - от sql до python. Идея проста: нужно хранить несколько значений друг с другом в одной конструкции и иметь доступ к ним по индексу. Условно говоря это такой массив фиксированной длины, у которого элементы могут быть разного типа.
И тут появляется традиционный вопрос - а зачем? Их много раз предлагали, но каждый раз упирались в вопрос "А чем мало простых структур?". И ведь правда, обычно структур должно хватать за глаза, а там где очень хочется можно притянуть множественный возврат из функции (тоже, кстати, своего рода кортеж). Но сила кортежей не в инновациях, а в облегчении каждодневной разработки. А именно в этих двух вещах:
• Для кортежа не нужно создавать отдельный тип (но можно). Ближайший аналог это анонимная структура, только в случае кортежа не нужно указывать имена полей.
• Для распаковки и упаковки кортежей в несколько переменных в язык добавляются специальные конструкции.
Посмотря на количество предложений, ребята наконец назрели и решили провести эксперимент по итогу которого сделали свое предложение: в их случае синтаксис выглядит как-то так:
Так-же можно получить конкретный элемент:
Функции тоже могут возвращать кортежи, но выглядит это немного странно (по причине обратной совместимости):
Однако по мнению Go Core Team это не настолько нужно, чтобы внедрять принципиально новый тип в язык. Поэтому сам proposal это даже не предложение, а скорее заметка о том, какое исследование они провели внутри Google и какие выводы сделали. Выводы кстати простые: делать они эту фичу не будут.
На мой взгляд, здесь они правы, так как для полноценного внедрения кортежей нужно было бы решать вопрос с возвратом функций без этих ужасных двойных скобок. Да и анонимные структуры решают 80% описанных проблем. Однако само исследование породило интересные выводы о том как можно улучшить язык не внедряя новые типы. Об этом мы поговорим в следующий раз.
P.S. И да я очень надеюсь вывести количество заметок хотя-бы до трех в месяц 😆️️️️️️
Я наконец оправился после конференции, а ребята из Go Core Team ненадолго вернулись к обсуждениям после ноябрьских праздников в США, перед тем как снова уйдут уже на рождественский отдых.
Итак, на повестке дня у нас новый тип - кортеж. Вообще кортежи много где встречаются - от sql до python. Идея проста: нужно хранить несколько значений друг с другом в одной конструкции и иметь доступ к ним по индексу. Условно говоря это такой массив фиксированной длины, у которого элементы могут быть разного типа.
И тут появляется традиционный вопрос - а зачем? Их много раз предлагали, но каждый раз упирались в вопрос "А чем мало простых структур?". И ведь правда, обычно структур должно хватать за глаза, а там где очень хочется можно притянуть множественный возврат из функции (тоже, кстати, своего рода кортеж). Но сила кортежей не в инновациях, а в облегчении каждодневной разработки. А именно в этих двух вещах:
• Для кортежа не нужно создавать отдельный тип (но можно). Ближайший аналог это анонимная структура, только в случае кортежа не нужно указывать имена полей.
• Для распаковки и упаковки кортежей в несколько переменных в язык добавляются специальные конструкции.
Посмотря на количество предложений, ребята наконец назрели и решили провести эксперимент по итогу которого сделали свое предложение: в их случае синтаксис выглядит как-то так:
val := (1, "foo", false)
x, y, z := val...
fn(val...) // вызываем функцию в которую распаковываем кортеж
func fn(int, string, bool) { ... }
Так-же можно получить конкретный элемент:
val := (1, "foo", false)
x := val.0 // получаем первый элемент
Функции тоже могут возвращать кортежи, но выглядит это немного странно (по причине обратной совместимости):
func f() ((int, int)) { // обратите внимание на двойные скобки
Однако по мнению Go Core Team это не настолько нужно, чтобы внедрять принципиально новый тип в язык. Поэтому сам proposal это даже не предложение, а скорее заметка о том, какое исследование они провели внутри Google и какие выводы сделали. Выводы кстати простые: делать они эту фичу не будут.
На мой взгляд, здесь они правы, так как для полноценного внедрения кортежей нужно было бы решать вопрос с возвратом функций без этих ужасных двойных скобок. Да и анонимные структуры решают 80% описанных проблем. Однако само исследование породило интересные выводы о том как можно улучшить язык не внедряя новые типы. Об этом мы поговорим в следующий раз.
P.S. И да я очень надеюсь вывести количество заметок хотя-бы до трех в месяц 😆️️️️️️
GitHub
proposal: Tuple Types for Go · Issue #64457 · golang/go
NOTE: This is a write-up of an internally worked out idea for tuple types, inspired by some of the tuple proposals listed below. The primary reason for this write-up is simply to document this work...
Немного обновлений тех предложений которые рассмотрены выше:
- slices: have Delete clear the tail: принято и уже реализовано. Ждем в 1.22 и в пакете exp для тех кто пока не может обновиться.
- cmd/go: add support for dealing with flaky tests: принято, но реализации пока ждет (судя по всему не ранее 1.23)
- spec: memoization (likely decline): ожидаемо отклонено и закрыто.
- intern package proposal: принято, хотя и с оговорками. Теперь это unique: new package with unique.Handle. Реализации пока нет.
Про итераторы: им быть, но в 1.22 они будут скрыты за флагом GOEXPERIMENT. Нужно для дополнительной полировки как самих изменений так и новых функций и типов в стандартной библиотеке. Зато точно появится новый формат записи циклов for о чем я говорил вот тут.
- slices: have Delete clear the tail: принято и уже реализовано. Ждем в 1.22 и в пакете exp для тех кто пока не может обновиться.
- cmd/go: add support for dealing with flaky tests: принято, но реализации пока ждет (судя по всему не ранее 1.23)
- spec: memoization (likely decline): ожидаемо отклонено и закрыто.
- intern package proposal: принято, хотя и с оговорками. Теперь это unique: new package with unique.Handle. Реализации пока нет.
Про итераторы: им быть, но в 1.22 они будут скрыты за флагом GOEXPERIMENT. Нужно для дополнительной полировки как самих изменений так и новых функций и типов в стандартной библиотеке. Зато точно появится новый формат записи циклов for о чем я говорил вот тут.
Telegram
Go Update
🦎 proposal: slices: have Delete clear the tail
В своем докладе на GolangConf 2023 я буду рассказывать про эволюцию Go, в том числе и про новые пакеты в стандартной библиотеке. Изучая один из этих пакетов мне стало интересно, как там реализована функция Delete.…
В своем докладе на GolangConf 2023 я буду рассказывать про эволюцию Go, в том числе и про новые пакеты в стандартной библиотеке. Изучая один из этих пакетов мне стало интересно, как там реализована функция Delete.…
🆒 Fixing For Loops in Go 1.22
Рассказывая про счастливое далекое будущее, я часто забываю рассказать про хорошее (почти) настоящее. Но перед тем как начать, я задам вопрос: а что выведет следующая программа?
Те из вас кто давно знаком с языком скорее всего подозревают что здесь есть подвох, но вероятно сходу заметить его не смогут. Новички же ждут примерно такой вывод:
Пробуем запустить и видим совсем другой ответ: https://go.dev/play/p/5m7YTUeNjfl Возникает вопрос: почему?
В Go внутри цикла, на каждой итерации, испольузется одна и та же переменная. Фактически компилятор переписывает наш код вот в такую конструкцию:
Однако это еще не все. Обратите внимание, что toStringer свою структуру получает по указателю. Для нас это происходит неявно, из-за того как работает вызов методов в Go. Дальше он конвертирует себя-по-указателю в тип fmt.Stringer и спокойно возвращает для вставки в слайс.
Проблема тут в том, что мы берем указатель на одну и ту же переменную все три итерации и каждый раз добавляем ее в слайс. И если во втором примере, это еще более менее очевидно, то в первом нужно знать о том, что циклы в Go создают переменную не на итерацию, а на весь цикл сразу. При этом Go тут не одинок - в JS и C# изначально было так же и создатели, впоследствии, решали ровно те же проблемы. В свое время (2012ый год ЕМНИП) Расс Кокс был уверен, что такую ошибку он никогда не допустит. Но сейчас, более десяти лет спустя, он был вынужден признать, что при всем своем опыте и знании Go, он совершил не один десяток подобных ошибок.
Поэтому начиная с версии Go 1.22 переменная цикла будет привязана к конкретной итерации, а не к телу цикла. Это значит, что компилятор будет трансформировать наш код немного иначе:
И теперь на каждую итерацию у нас есть своя уникальная переменная. При этом компилятор достаточно умен и для уменьшения потери производительности, будет смотреть в тело цикла. И там где это корректно, будет компилировать код в первый вариант. Первичный анализ показал, что большинство циклов не потеряли в производительности.
Однако это все-же ломающее изменение, поэтому включено оно будет только в проекте где go.mod ставит версию go 1.22.0 и выше и только для самого модуля (модули которые вы импортируете продолжат работать по старому если сами не перейдут на go 1.22).
А попробовать можно уже сейчас:
- На playground через управляющий комментарий (работает только там) либо через Go dev branch. https://go.dev/play/p/QjEKVbvX59-
- Локально, если стоит переменная окружения GOEXPERIMENT=loopvar и у вас Go >= 1.21.0
Рассказывая про счастливое далекое будущее, я часто забываю рассказать про хорошее (почти) настоящее. Но перед тем как начать, я задам вопрос: а что выведет следующая программа?
package main
import "fmt"
func main() {
var slc []fmt.Stringer
for _, s := range []st{{"Hello "}, {"World"}, {"!"}} {
slc = append(slc, s.toStringer())
}
fmt.Print(slc)
}
type st struct{ str string }
func (s *st) String() string { return s.str }
func (s *st) toStringer() fmt.Stringer { return s }
Те из вас кто давно знаком с языком скорее всего подозревают что здесь есть подвох, но вероятно сходу заметить его не смогут. Новички же ждут примерно такой вывод:
[Hello World !]
Пробуем запустить и видим совсем другой ответ: https://go.dev/play/p/5m7YTUeNjfl Возникает вопрос: почему?
В Go внутри цикла, на каждой итерации, испольузется одна и та же переменная. Фактически компилятор переписывает наш код вот в такую конструкцию:
temp := []st{{"Hello"}, {"World"}, {"!"}}
if len(temp) > 0 {
var s st
for i := 0; i < len(temp); i++ {
s = temp[i]
slc = append(slc, s.toStringer())
}
}
Однако это еще не все. Обратите внимание, что toStringer свою структуру получает по указателю. Для нас это происходит неявно, из-за того как работает вызов методов в Go. Дальше он конвертирует себя-по-указателю в тип fmt.Stringer и спокойно возвращает для вставки в слайс.
Проблема тут в том, что мы берем указатель на одну и ту же переменную все три итерации и каждый раз добавляем ее в слайс. И если во втором примере, это еще более менее очевидно, то в первом нужно знать о том, что циклы в Go создают переменную не на итерацию, а на весь цикл сразу. При этом Go тут не одинок - в JS и C# изначально было так же и создатели, впоследствии, решали ровно те же проблемы. В свое время (2012ый год ЕМНИП) Расс Кокс был уверен, что такую ошибку он никогда не допустит. Но сейчас, более десяти лет спустя, он был вынужден признать, что при всем своем опыте и знании Go, он совершил не один десяток подобных ошибок.
Поэтому начиная с версии Go 1.22 переменная цикла будет привязана к конкретной итерации, а не к телу цикла. Это значит, что компилятор будет трансформировать наш код немного иначе:
temp := []st{{"Hello"}, {"World"}, {"!"}}
if len(temp) > 0 {
for i := 0; i < len(temp); i++ {
var v st
v = temp[i]
slc = append(slc, v.toStringer())
}
}
И теперь на каждую итерацию у нас есть своя уникальная переменная. При этом компилятор достаточно умен и для уменьшения потери производительности, будет смотреть в тело цикла. И там где это корректно, будет компилировать код в первый вариант. Первичный анализ показал, что большинство циклов не потеряли в производительности.
Однако это все-же ломающее изменение, поэтому включено оно будет только в проекте где go.mod ставит версию go 1.22.0 и выше и только для самого модуля (модули которые вы импортируете продолжат работать по старому если сами не перейдут на go 1.22).
А попробовать можно уже сейчас:
- На playground через управляющий комментарий (работает только там) либо через Go dev branch. https://go.dev/play/p/QjEKVbvX59-
- Локально, если стоит переменная окружения GOEXPERIMENT=loopvar и у вас Go >= 1.21.0
go.dev
Fixing For Loops in Go 1.22 - The Go Programming Language
Go 1.21 shipped a preview of a change in Go 1.22 to make for loops less error-prone.
🎄 Ежегодное обязательное поздравление с новым годом.
Я долго думал над тем, что написать тут. Поэтическое оставило меня, но два слова я думаю мне связать удастся.
Это былсложный интересный год для Go. В стандартую библиотеку наконец добрались функции и типы на дженериках, поэтому их изучение стало не прихотью а необходимостью. Создатели языка взялись за исправление ошибок дизайна, при этом не забывая о тех, кто построил поведение своих приложений вокруг этих особенностей. Кроме того продолжился тренд на улучшение языка и рантайма. Были, впрочем, и те вещи которые создатели языка пока улучшать не готовы. В следующем году я надеюсь на продолжение этого тренда: внедрение нового не забывая о старом.
Каждому из вас, в новом году, я желаю того же - уверенно двигайтесь в будущее, не забывая о прошлом. Я очень рад и благодарен каждому из вас за интерес к теме развития языка Go - нас уже больше 1300, что для канала с очень специфичными интересами я считаю крайне хорошим результатом. А я в свою очередь продолжу о освещать важные и интересные нововведения и обсуждения вокруг языка.
С новым годом! И тех у кого он уже наступил, и тех у кого он только наступит. И пускай все будет!
Я долго думал над тем, что написать тут. Поэтическое оставило меня, но два слова я думаю мне связать удастся.
Это был
Каждому из вас, в новом году, я желаю того же - уверенно двигайтесь в будущее, не забывая о прошлом. Я очень рад и благодарен каждому из вас за интерес к теме развития языка Go - нас уже больше 1300, что для канала с очень специфичными интересами я считаю крайне хорошим результатом. А я в свою очередь продолжу о освещать важные и интересные нововведения и обсуждения вокруг языка.
С новым годом! И тех у кого он уже наступил, и тех у кого он только наступит. И пускай все будет!
🎉 Состоялся релиз Go 1.22
Как-то буднично и без предварительных фанфар состоялся релиз новой версии языка. Изменений много, постараюсь остановится лишь на самых значимых:
— Расширение синтаксиса циклов for. Теперь можно писать
Вместо
Изменение приятное, уменьшающее число когнитивной нагрузки. Само изменение шло в довесок к итераторам, которые отложили до Go 1.23 (но которые можно попробовать уже сейчас).
— Изменение принципов создания переменных внутри объявления циклов. Об этом я писал вот тут, но если в кратце больше не нужна конструкция вида
— Итераторы доступны в экспериментальном режиме. Включить и поиграться можно через переменную окружения
—
— Переделали trace – и пакет и UI.
—
— Первый v2 пакет
— PGO (оптимизация использующая данные профилировщика) теперь генерирует еще более быстрый код. Обещают от 2% др 14% прироста производительности при использовании PGO.
— Оптимизации "встраивание функций" и "девиртуализатор" теперь работают совместно, что позволяет выполнять один после другого и обратно. Этого очень просили пользователи функций криптографических пакетов которые возвращают интерфейсы.
— Оптимизацию "встраивание функций" сделали еще более умной - теперь она пытается отработать внутри циклов и других горячих местах, и наоборот пытается не инлайнить в коде обработки паник. Но пока все это тоже в экспериментальном режиме. Попробовать можно через
— Из лично приятного: в пакет
На мой взгляд релиз в целом приятный, но половинчатый: многое из действительно интересных вещей скрыты за флагом
Полный список изменений как всегда тут.
Как-то буднично и без предварительных фанфар состоялся релиз новой версии языка. Изменений много, постараюсь остановится лишь на самых значимых:
— Расширение синтаксиса циклов for. Теперь можно писать
for i := range 10 {
println(i)
}
Вместо
for i := 0; i < 10; i++ {
println(i)
}
Изменение приятное, уменьшающее число когнитивной нагрузки. Само изменение шло в довесок к итераторам, которые отложили до Go 1.23 (но которые можно попробовать уже сейчас).
— Изменение принципов создания переменных внутри объявления циклов. Об этом я писал вот тут, но если в кратце больше не нужна конструкция вида
tt := tt
внутри циклов.— Итераторы доступны в экспериментальном режиме. Включить и поиграться можно через переменную окружения
GOEXPERIMENT=rangefunc
. Можно установить через go env -w GOEXPERIMENT=rangefunc
если не хочется каждый раз возится. В комплекте так-же идет пакет iter
который позволяет создавать pull итераторы из push. Почитать про все это от разработчиков языка можно тут.—
go test -cover
теперь корректно выводит 0% покрытия для пакетов где нет тестов, но есть исполняемый код. Для пакетов где нет go файлов или они содержат только структуры выводит старое [no test files]
.— Переделали trace – и пакет и UI.
—
net/http
роутер теперь поддерживает указание метода и паттерны. Про это расширение роутера было много статей и блогов, поэтому тут будет просто упоминание.— Первый v2 пакет
math/rand/v2
. Заменили Mitchell & Reeds LFSR
генератор rand.Source
случайных чисел на более современный и криптографически стойкий ChaCha8
. А сие значит, что его можно использовать для криптографических операций. Плюс он быстрее и жрет меньше памяти. Так-же есть PCG
генератор, который не криптографически стойкий, но еще быстрее. Кроме этого пакет получил дополнительные методы (в том числе дженерик функция rand.N
для работы с семейством int
типов, например time.Duration
).— PGO (оптимизация использующая данные профилировщика) теперь генерирует еще более быстрый код. Обещают от 2% др 14% прироста производительности при использовании PGO.
— Оптимизации "встраивание функций" и "девиртуализатор" теперь работают совместно, что позволяет выполнять один после другого и обратно. Этого очень просили пользователи функций криптографических пакетов которые возвращают интерфейсы.
— Оптимизацию "встраивание функций" сделали еще более умной - теперь она пытается отработать внутри циклов и других горячих местах, и наоборот пытается не инлайнить в коде обработки паник. Но пока все это тоже в экспериментальном режиме. Попробовать можно через
GOEXPERIMENT=newinliner
. Почитать тут.— Из лично приятного: в пакет
slices
добралась функция Concat
для соединения произвольного числа слайсов. Больше не нужно городить цепочку append
.На мой взгляд релиз в целом приятный, но половинчатый: многое из действительно интересных вещей скрыты за флагом
GOEXPERIMENT
, а часть вообще осталась ждать Go 1.23. Тем не менее обновится стоит, хотя-бы ради нового синтаксиса циклов for.Полный список изменений как всегда тут.
Telegram
Go Update
🆒 Fixing For Loops in Go 1.22
Рассказывая про счастливое далекое будущее, я часто забываю рассказать про хорошее (почти) настоящее. Но перед тем как начать, я задам вопрос: а что выведет следующая программа?
package main
import "fmt"
func main() {
var…
Рассказывая про счастливое далекое будущее, я часто забываю рассказать про хорошее (почти) настоящее. Но перед тем как начать, я задам вопрос: а что выведет следующая программа?
package main
import "fmt"
func main() {
var…
Вопрос: может кто знает сервис который markdown переводит в понятный телеге формат? Уже несколько раз бьюсь о то, что телега не умеет нормально парсить markdown ссылки, из-за чего приходиться редактировать уже в окне набора сообщений, что очень неудобно. И ведь явно эту проблему как-то решали?
Про итераторы.
Решил снова поиграться с итераторами на довольно простом примере
Первое впечатление, это реально круто. На простом коде это скорее ведет к ухудшению восприятия кода, но на больших цепочках вызовов (например те которые берут данные из хранилища) это будет просто палочкой выручалочкой в плане потребления памяти и организации кода.
Однако есть минусы:
– Сильно ощущается отсутствие параметрических методов на типах. Я знаю причины, более того я знаю даже детали этих причин и всю подноготную сложность решения этой проблемы. Но блин, создавать отдельные переменные для вызова
– Итераторы
В остальном, пока очень доволен. Надо посмотреть на производительность, но в целом это близко к революции которую произвели дженерики два года назад.
Поиграться можно тут https://go.dev/play/p/aDX94GK8rYj?v=gotip
П.С. Библиотеки для работы с итераторами уже тоже начали появляться, например https://github.com/BooleanCat/go-functional/blob/main/v2/iter/count_test.go
Решил снова поиграться с итераторами на довольно простом примере
package main
import (
"fmt"
"iter"
"strconv"
)
func IterSlice[V any](slc []V) iter.Seq[V] {
return func(yield func(V) bool) {
for i := range len(slc) {
if !yield(slc[i]) {
return
}
}
}
}
func Map[V any, R any](seq iter.Seq[V], fn func(V) R) iter.Seq[R] {
return func(yield func(R) bool) {
for v := range seq {
if !yield(fn(v)) {
return
}
}
}
}
func Limit[V any](seq iter.Seq[V], limit int) iter.Seq[V] {
return func(yield func(V) bool) {
i := 0
for v := range seq {
if i >= limit {
return
}
if !yield(v) {
return
}
i++
if i >= limit {
return
}
}
}
}
func main() {
slc := []int{1, 2, 3, 4, 5}
imulIter := Map(Limit(IterSlice(slc), 3), func(v int) int { return v * 2 })
strIter := Map(imulIter, func(v int) string { return strconv.Itoa(v) })
strIter = Map(strIter, func(v string) string { return "Number is " + v })
for v := range strIter {
fmt.Println(v)
}
}
Первое впечатление, это реально круто. На простом коде это скорее ведет к ухудшению восприятия кода, но на больших цепочках вызовов (например те которые берут данные из хранилища) это будет просто палочкой выручалочкой в плане потребления памяти и организации кода.
Однако есть минусы:
– Сильно ощущается отсутствие параметрических методов на типах. Я знаю причины, более того я знаю даже детали этих причин и всю подноготную сложность решения этой проблемы. Но блин, создавать отдельные переменные для вызова
Map
выглядит, прямо скажем, не очень удобно. А лесенка из скобочек напоминает лисп, но выглядит уродливо. – Итераторы
Seq[V]
и Seq2[K,V]
. Опять же ощущается отсутствие кортежей на уровне языка, т.к. получается необходимость писать функции для двух типов итераторов: с одиночными значениями и для пар ключ-значения. Про эту проблему знают, но полного решения ее похоже не предвидится. Текущий консенсус в том, что функции нужно написать всего один раз.В остальном, пока очень доволен. Надо посмотреть на производительность, но в целом это близко к революции которую произвели дженерики два года назад.
Поиграться можно тут https://go.dev/play/p/aDX94GK8rYj?v=gotip
П.С. Библиотеки для работы с итераторами уже тоже начали появляться, например https://github.com/BooleanCat/go-functional/blob/main/v2/iter/count_test.go
go.dev
Go Playground - The Go Programming Language
⌛ runtime: special case timer channels.
У таймеров в Go есть интересная особенность: если на них не вызвать метод
Сейчас корректная работа с таймерами выглядит примерно вот так:
Issue которое описывает проблему и потенциальное решение существует уже почти 10 лет. Более того, Расс хотел придумать и внедрить решение еще в Go 1.7. Однако время шло и к проблеме никто не возвращался.
В июле 2023го (спустя почти 9 лет) Расс наконец предложил решение в новом issue. Решение не успевало попасть в Go 1.21, но была надежда, что оно попадет в Go 1.22. Более того, в августе решение было одобрено, а так как PR уже был готов, была мысль, что оно попадет в мастер как можно скорее. Однако время продолжало идти, а PR висел и висел. К своему смущению, я был уверен, что проблема была решена в 1.22.
И вот наконец, момент настал: https://github.com/golang/go/commit/508bb17edd04479622fad263cd702deac1c49157. И даже документация теперь отражает, что вызов
Ну что, как говорится «лучше поздно, чем никогда». Теперь
П.С. Если вам зачем-то нужно старое поведение, то переменная окружения
У таймеров в Go есть интересная особенность: если на них не вызвать метод
Stop
то сборщик мусора не соберет их пока они не закончат свою работу или не будут явно остановлены. И если в случае функции time.After
(которая возвращает канал) время сборки мусора отодвигалось до срабатывания таймера, то тикер time.Tick
(функция которая тоже возвращает канал) не будет собран сборщиком мусора вообще никогда. Документация, конечно, объясняет эти моменты, но хотелось бы, что-бы рантайм делал свою работу а не оставлял мусор из-за особенностей работы языка. В частности из-за этой особенности time.Tick
невозможно было применять в реальных долгоживущих приложениях. Сейчас корректная работа с таймерами выглядит примерно вот так:
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
// some other cases
// ...
case t := <-ticker.C:
fmt.Println("Current time: ", t)
return
}
}
Issue которое описывает проблему и потенциальное решение существует уже почти 10 лет. Более того, Расс хотел придумать и внедрить решение еще в Go 1.7. Однако время шло и к проблеме никто не возвращался.
В июле 2023го (спустя почти 9 лет) Расс наконец предложил решение в новом issue. Решение не успевало попасть в Go 1.21, но была надежда, что оно попадет в Go 1.22. Более того, в августе решение было одобрено, а так как PR уже был готов, была мысль, что оно попадет в мастер как можно скорее. Однако время продолжало идти, а PR висел и висел. К своему смущению, я был уверен, что проблема была решена в 1.22.
И вот наконец, момент настал: https://github.com/golang/go/commit/508bb17edd04479622fad263cd702deac1c49157. И даже документация теперь отражает, что вызов
Stop
на таймерах и тикерах больше не обязателен.Ну что, как говорится «лучше поздно, чем никогда». Теперь
time.Tick
можно использовать в горячих местах. Еще одно место для легких правок в 1.23.П.С. Если вам зачем-то нужно старое поведение, то переменная окружения
GODEBUG=asynctimerchan=1
даст вашей программе старый вариант.GitHub
runtime: special case timer channels · Issue #8898 · golang/go
Consider special-casing timer channels created with time.Ticker and time.After. Namely, such chans need to contain next fire time and period. Then receive from such looks like: func chanrecv(c *Hch...
Новый материал по обновлениям в стандартной библиотеке (и не только) Go уже в работе.
А пока я его пишу, вот вам небольшая викторина навеянная недавним багом который я искал в проде.
Что выведет код:
А пока я его пишу, вот вам небольшая викторина навеянная недавним багом который я искал в проде.
init[1055]: segfault at 40 ip 00000000008ef4f8 sp 000000c000e23500 error 4 in init[400000+245f000] likely on CPU 0 (core 0, socket 0)
Что выведет код:
type MyString string
func (s *MyString) String() string { return "world! " + string(*s) }
func main() {
var str *MyString
fmt.Println("Hello", str)
}
Forwarded from Dmitry M
Ваш ответ
Final Results
11%
Hello world!
16%
Hello <panic: nil…>
39%
Панику
29%
Hello <nil>
5%
Hello
А теперь повысим сложность для тех кому интересно:
Ядро очень чутко относится к процессу с PID 1. Оно и понятно: нет процесса — нет юзерспейса ОС. Segfault который вы видели выше, это как-раз и есть эта самая паника с точки зрения ядра. Которое ее видит и пишет в логи. Отсюда получаем неприятный момент — паника как-бы есть, но видим мы ее только потому-что наш Go процесс служит init процессом. Другая возможность увидеть эту панику это подключится к процессу через дебаггер, но с init процессом это довольно проблематично. А вот тесты в таком сценарии не особо помогают.
Я это к чему: было несколько предложений в трекере про функцию для установки глобального перехватчика на паники. Но Go Core Team с упорством каждый раз отклоняет их с фразой «не нужно, есть
Ядро очень чутко относится к процессу с PID 1. Оно и понятно: нет процесса — нет юзерспейса ОС. Segfault который вы видели выше, это как-раз и есть эта самая паника с точки зрения ядра. Которое ее видит и пишет в логи. Отсюда получаем неприятный момент — паника как-бы есть, но видим мы ее только потому-что наш Go процесс служит init процессом. Другая возможность увидеть эту панику это подключится к процессу через дебаггер, но с init процессом это довольно проблематично. А вот тесты в таком сценарии не особо помогают.
Я это к чему: было несколько предложений в трекере про функцию для установки глобального перехватчика на паники. Но Go Core Team с упорством каждый раз отклоняет их с фразой «не нужно, есть
recover()
». Вроде и да, но вот в таких случаях нас спасает только вывод go tool objdump -S
и знание ассемблера. И как улучшить ситуацию я, честно говоря, не знаю.
HTML Embed Code: