Go Update
Канал про новости связанные с языком программирования Go. Эволюция языка, стандартной библиотеки и просто интересные вещи над которыми работает Go Core Team и не только. Админ: @lepage_d
إظهار المزيد- المشتركون
- التغطية البريدية
- ER - نسبة المشاركة
جاري تحميل البيانات...
جاري تحميل البيانات...
1.
Функция работает только с теми паниками, которые никто не перехватил используя recover()
.
2.
Функция позволяет выбрать только куда выводить сообщение. А если точнее, в какой файловый дескриптор направить вывод.
Т.е. общая идея данной функции состоит в том, что-бы разработчик мог выбрать куда конкретно он будет писать фатальные паники при схлопывании приложения. В документации есть удобный и показательный пример. А кому интересна внутрянка, то все самое важное происходит вот тут. Число изменений минимальное - Go рантайм уже использует файловый дескриптор когда пишет текст паники, поэтому все, что мы (условно) делаем это атомарно подменяем этот самый дескриптор в функции SetCrashOutput
.
Однако для случая выше это бы никак не помогло, т.к. паника уже перехвачена с помощью recover()
во внутрянке пакета fmt
. Моя идея состояла скорее в глобальном хуке, который позволяет вклиниться в любую панику до ее обработки. Но именно для анализа, а не для остановки или восстановления т.к. для этого уже есть другие механизмы.
П.С. Спасибо Алексею Палажченко за наводку.Package debug contains facilities for programs to debug themselves while they are running.
recover()
». Вроде и да, но вот в таких случаях нас спасает только вывод go tool objdump -S
и знание ассемблера. И как улучшить ситуацию я, честно говоря, не знаю.1.
Как работает вызов по nil
указателю.
2.
Как работает семейство fmt.*Print*
функций.
3.
Какие паники можно ловить через recover
.
Во первых вызов по nil
указателю это вполне корректный код в отличии от, например, Java или C++. До тех пор пока вы не обращаетесь к данным внутри переменной типа, никаких ограничений нет язык не накладывает. Этим пользуются например в grpc генераторах для создания типов с цепочками методов — даже если переменная nil
она все равно может быть рабочей. Единственное но: это работает только с конкретными типами. Для интерфейсов нужно что-бы переменная-интерфейс знала о том какой у нее тип внутри. Да-да тот самый typed nil про который любят спрашивать на собесах иногда имеет реальную применимость.
Для второго идем читаем документацию https://pkg.go.dev/fmt#hdr-Format_errors где видим:
If an Error or String method triggers a panic when called by a print routine, the fmt package reformats the error message from the panic, decorating it with an indication that it came through the fmt package. For example, if a String method calls panic("bad"), the resulting formatted message will look like %!s(PANIC=bad) The %!s just shows the print verb in use when the failure occurred. If the panic is caused by a nil receiver to an Error or String method, however, the output is the undecorated string, "<nil>".Т.е. в случае паники
fmt.Println
постарается перехватить ее и вывести, что было внутри паники. Исключением являются попытки разыменования по нулевому указателю, вывод паники которой даст <nil>
. Почитать исходники можно тут: https://go.googlesource.com/go/+/refs/tags/go1.22.2/src/fmt/print.go#587
И наконец третье - перехватить через recover
можно любую панику кроме паники о одновременном чтения и записи (или одновременной записью из разных горутин) в тип map
. Вызов runtime.Goexit()
перехватить тоже нельзя, т.к. он не является паникой.Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
- Hello world!
- Hello <panic: nil…>
- Панику
- Hello <nil>
- Hello
init[1055]: segfault at 40 ip 00000000008ef4f8 sp 000000c000e23500 error 4 in init[400000+245f000] likely on CPU 0 (core 0, socket 0)
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
даст вашей программе старый вариант.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...
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) {
seq(func(v V) bool {
return yield(fn(v))
})
}
}
func Limit[V any](seq iter.Seq[V], limit int) iter.Seq[V] {
return func(yield func(V) bool) {
i := 0
seq(func(v V) bool {
if i < limit {
i++
return yield(v)
}
return false
})
}
}
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рен
ие синтаксиса циклов 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.
Полный список изменений как всегда тут.🆒 Fixing For Loops in Go 1.22 Рассказывая про счастливое далекое будущее, я часто забываю рассказать про хорошее (почти) настоящее. Но перед тем как начать, я задам вопрос: а что выведет следующая программа? 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 {…
تسمح خطتك الحالية بتحليلات لما لا يزيد عن 5 قنوات. للحصول على المزيد، يُرجى اختيار خطة مختلفة.