Полезные фичи новой версии Go — 1.26
- четверг, 19 февраля 2026 г. в 00:00:17
Всем привет! Меня зовут Паша Агалецкий, я техлид команды платформы разработки Авито. В этой статье я расскажу о самых интересных и полезных фичах релиза новой версии Go — 1.26. Статья будет полезна всем, кто работает с Go и следит за развитием языка.

Раньше оператор new можно было использовать только вместе с типами — он создавал значение заданного типа и возвращал указатель на него. Например:
v := new(bool) *v = true fmt.Println(v)
На практике часто требуется создать указатель на конкретное значение. Особенно это заметно при работе с JSON или различными DTO. Например:
type User struct { Name string Enabled *bool } v := true u := User{ Name: "Ivan", Enabled: &v // приходится создавать дополнительную переменную! } fmt.Println(u) Чтобы упростить код, многие добавляли функции-хелперы. Например: func ToPtr[T any](in T) *T { return &in }
Тогда код выглядел так:
type User struct { Name string Enabled *bool } u := User{ Name: "Ivan", Enabled: ToPtr(true) // код становится проще } fmt.Println(u)
В Go 1.26 new можно применять не только к типам, но и к выражениям. Поэтому пример выше можно переписать так:
type User struct { Name string Enabled *bool } u := User{ Name: "Ivan", Enabled: new(true) // не нужны вспомогательные переменные или функции } fmt.Println(u)
Теперь возможны следующие вызовы:
new(42) // int new("hello world") // string new([]int{1, 2, 3}) // composite new(f()) // result of function call
Код становится проще и понятнее.
Один из паттернов работы с ошибками в Go — проверка, что возвращённый интерфейс error содержит внутри себя конкретный тип ошибки. Для этого используется функция errors.As. Например:
type ValidationError struct {} // ... err := performOperation() var target *ValidationError if errors.As(err, &target) { fmt.Println("validation error:", target) }
Даже из этого примера видно, что код получается довольно многословным: необходимо объявить промежуточную переменную, вызвать errors.As и только затем работать с результатом.
В Go 1.26 появился новый вспомогательный метод errors.AsType. Он делает код лаконичнее, явно выражает намерение и избавляет от промежуточной переменной. Вот как выглядит пример выше:
type ValidationError struct {} // ... err := performOperation() if target, ok := errors.AsType[*ValidationError](err); ok { fmt.Println("validation error:", target) }
Ещё стало удобнее последовательно проверять несколько типов ошибок:
err := performOperation() if target, ok := errors.AsType[*ValidationError](err); ok { fmt.Println("validation error:", target) } else if target, ok := errors.AsType[*DomainError](err); ok { fmt.Println("domain error:", target) }
Новый метод работает быстрее, чем errors.As, не использует reflect под капотом и позволяет избегать паник в runtime, которые могли быть связаны с некорректными типами, передаваемыми в errors.As.
В некоторых случаях требуется скрывать в памяти приватные или чувствительные данные так, чтобы даже при снятии дампа памяти процесса атакующая сторона не смогла получить к ним доступ. Например, при работе с криптографическими ключами.
Go runtime не гарантирует, в какой момент будет очищена память, выделенная программе. Поэтому даже после завершения вызова метода нет гарантии, что память будет очищена немедленно.
В Go 1.26 появился новый экспериментальный пакет secret, который содержит пока один метод — secret.Do. Фича в том, что после завершения работы secret.Do вся память, выделенная внутри него, очищается сразу после выхода. Это даёт дополнительную защиту чувствительных данных.
Использовать secret.Do можно так:
func SessionKey(publicKey string) string { var sessionKey string secret.Do(func() { privateKey := generatePrivateKey() sessionKey = generateSessionKey(privateKey, publicKey) } // в этом месте память privatekey очищается сразу, // sessionKey — остаётся доступным return sessionKey }
Пока secret.Do доступен только в экспериментальном режиме — через флаг GOEXPERIMENT=runtimesecret. Собирать проект нужно так:
GOEXPERIMENT=runtimesecret go build .
В Go 1.25 представили новый garbage collector под названием GreenTea. Его цель — оптимизировать и ускорить сборку мусора на современных многоядерных процессорах. Изначально он был доступен в экспериментальном режиме, а в версию 1.26 его включили по умолчанию.
Новый GC разделяет всю память на блоки (spans), которые сканируются целиком. Это ускоряет работу в случаях, когда приложение выделяет много относительно маленьких объектов. При этом освобождение памяти происходит не сразу после того, как объект внутри span помечается неиспользуемым, а когда таких объектов накапливается несколько.
Бенчмарки показывают прирост производительности в диапазоне 10–40% в зависимости от нагрузки.
Отключить новый GC можно с помощью специального флага:
```bash GOEXPERIMENT=nogreenteagc ```
Этот флаг временный — в Go 1.27 его планируют удалить.
В Go 1.26 появился новый экспериментальный пакет simd/archsimd, который даёт возможность использовать SIMD-инструкции, доступные на некоторых архитектурах, в частности на AMD64.
SIMD расшифровывается как Single Instruction Multiple Data. Такие процессорные инструкции позволяют выполнять операции сложения, умножения и другие вычисления параллельно сразу над большим количеством данных. Они полезны при реализации алгоритмов сжатия, шифрования, а также, например, инференса или обучения больших языковых моделей. Даже если сами не пишете такой код, вы можете пользоваться библиотеками, которые его содержат.
Пока пакет поддерживает только архитектуру AMD64. Это значит, что при разработке кроссплатформенного кода, например с поддержкой ARM, придётся дублировать логику: один вариант для AMD64 и отдельный — для других архитектур.
Из коробки пакет предоставляет операции для 128-, 256- и 512-битных векторов, например:
func main() { a := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} b := []int64{20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} res := Add(a, b) fmt.Printf("%v\n", res) } func Add(a, b []int64) []int64 { if archsimd.X86.AVX512() { return AddSIMD(a, b) } return AddPlain(a, b) } func AddSIMD(a, b []int64) []int64 { if len(a) != len(b) { panic("len(a) != len(b)") } res := make([]int64, len(a)) i := 0 for ; i < len(a)-8; i += 8 { la := archsimd.LoadInt64x8Slice(a[i : i+8]) lb := archsimd.LoadInt64x8Slice(b[i : i+8]) sum := la.Add(lb) sum.StoreSlice(res[i : i+8]) } for ; i < len(a); i++ { res[i] = a[i] + b[i] } return res } func AddPlain(a, b []int64) []int64 { if len(a) != len(b) { panic("len(a) != len(b)") } res := make([]int64, len(a)) for i := 0; i < len(a); i++ { res[i] = a[i] + b[i] } return res }
В этом примере (который собирается только под архитектуру AMD64) мы проверяем доступность инструкций AVX-512 и, если они поддерживаются, вызываем более быстрый код сложения. Если нет — складываем значения int64 по одному. В итоге получаем повышение производительности.
В Go 1.26 код с рекурсивными ограничениями типов теперь компилируется без ошибок.
Возьмём, например, такой код:
type Ordered[T Ordered[T]] interface { Less(T) bool }
Интерфейс Ordered говорит нам, что он «наследник» от любого типа, который, в свою очередь, сам тоже реализует Ordered. Зачем это может быть нужно?
Допустим, мы хотим написать обобщённый отсортированный список:
type SortedList[T Ordered[T]] struct { items []T }
Таким образом, мы говорим, что готовы принять любой тип, который сам по себе имеет метод Less.
Раньше, до Go 1.26, компиляция такого кода завершалась ошибкой:
invalid recursive type: T refers to itself
Теперь же код успешно компилируется. Небольшое, но приятное развитие возможностей дженериков.
В Go 1.26 изменилось поведение signal.NotifyContext: теперь в context.Cause возвращается конкретная причина отмены при получении сигнала.
Допустим, у нас есть код, который отменяет контекст при получении сигнала:
func main() { ctx := context.Background() ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() <-ctx.Done() fmt.Printf("Shutting down, reason: %v\n", context.Cause(ctx)) }
В Go 1.25 в качестве причины отмены контекста мы получили бы context canceled, а в Go 1.26 — interrupt signal received.
В этом случае context.Cause возвращает ошибку типа signalError:
type signalError string func (s signalError) Error() string { return string(s) }
К сожалению, отличить её как-то иначе, кроме как по тексту сообщения, сейчас нельзя, так как тип приватный и не содержит специальных маркирующих методов.
Традиционно считается, что если хотите вернуть просто текстовую ошибку без интерполяции значений, стоит использовать errors.New, а не fmt.Errorf. То есть:
errors.New("some error")
вместо
fmt.Errorf("some error")
так как последняя требует больше ресурсов.
Это не всегда удобно — приходится помнить, какую функцию вызывать в зависимости от аргументов.
В Go 1.26 добавили оптимизацию: теперь fmt.Errorf создаёт меньше аллокаций, и его использование с константной строкой стало практически эквивалентно errors.New.
В пакете slog появился новый тип MultiHandler и конструктор NewMultiHandler. Они реализуют популярный запрос — отправку логов сразу в несколько обработчиков. Например, один и тот же лог можно записать в stderr и параллельно отправить по сети во внешний коллектор.
stderr := slog.NewTextHandler(os.Stderr, nil) remote := collector.NewRemoteHandler() handler := slog.NewMultiHandler(remote, stderr) log := slog.New(handler) log.Info("hello world")
В итоге мы удобно отсылаем логи сразу в несколько обработчиков.
Команда go fix традиционно используется для небольших изменений Go-кода, но долгое время было довольно примитивной и не слишком полезной.
В Go 1.26 её обновили и переиспользовали механику, аналогичную go vet. Теперь разделение между этими командами стало очевидным:
go vet — линтинг;
go fix — автоматические исправления.
Основное назначение go fix теперь — применение современных практик к Go-коду.
Чтобы посмотреть, какие фиксы доступны, используйте команду go tool fix help.
Registered analyzers: any replace interface{} with any buildtag check //go:build and // +build directives fmtappendf replace []byte(fmt.Sprintf) with fmt.Appendf ... и другие forvar remove redundant re-declaration of loop variables hostport check format of addresses passed to net.Dial inline apply fixes based on 'go:fix inline' comment directives mapsloop replace explicit loops over maps with calls to maps package minmax replace if/else statements with calls to min or max newexpr simplify code by using go1.26's new(expr) omitzero suggest replacing omitempty with omitzero for struct fields plusbuild remove obsolete //+build comments rangeint replace 3-clause for loops with for-range over integers reflecttypefor replace reflect.TypeOf(x) with TypeFor[T]() slicescontains replace loops with slices.Contains or slices.ContainsFunc slicessort replace sort.Slice with slices.Sort for basic types stditerators use iterators instead of Len/At-style APIs stringsbuilder replace += with strings.Builder stringscut replace strings.Index etc. with strings.Cut stringscutprefix replace HasPrefix/TrimPrefix with CutPrefix stringsseq replace ranging over Split/Fields with SplitSeq/FieldsSeq testingcontext replace context.WithCancel with t.Context in tests waitgroup replace wg.Add(1)/go/wg.Done() with wg.Go Как видно, доступно довольно много различных анализаторов и фиксов. Например, можно легко заменить все interface{} на any: // было func doSomething(v interface{}) {} // после go fix func doSomething(v any) {}
Обновлённый go fix можно встроить в пайплайны проекта, чтобы поддерживать код в актуальном состоянии.
Релиз go 1.26 довольно большой: расширение возможностей оператора new , улучшение дженериков, новый GC, повышение производительности. Много улучшений, которые сделают жизнь разработчиков лучше.
Пакет json/v2, который появился в Go 1.25, в этом релизе пока остаётся за экспериментальным флагом GOEXPERIMENT=jsonv2.
Релиз получился крутым — уже не терпится попробовать его в проде.
А что думаете вы? Делитесь мнением в комментариях!
Узнать больше о задачах, которые решают инженеры AvitoTech, можно по этой ссылке. А вот тут мы собрали весь контент от нашей команды — там вы найдете статьи, подкасты, видео и много чего еще. И заходите в наш TG-канал, там интересно!