golang

Полезные фичи новой версии Go — 1.26

  • четверг, 19 февраля 2026 г. в 00:00:17
https://habr.com/ru/companies/avito/articles/1000616/

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

Содержание

Поддержка выражений в new 

changelog

Раньше оператор 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

Код становится проще и понятнее.

Тут еще больше контента

Проверка конкретного типа ошибки через errors.AsType

changelog

Один из паттернов работы с ошибками в 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.

Экспериментальный пакет secret для работы с чувствительными данными

changelog

В некоторых случаях требуется скрывать в памяти приватные или чувствительные данные так, чтобы даже при снятии дампа памяти процесса атакующая сторона не смогла получить к ним доступ. Например, при работе с криптографическими ключами.

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 .

Новый алгоритм сборки мусора по умолчанию

changelog

В Go 1.25 представили новый garbage collector под названием GreenTea. Его цель — оптимизировать и ускорить сборку мусора на современных многоядерных процессорах. Изначально он был доступен в экспериментальном режиме, а в версию 1.26 его включили по умолчанию. 

Новый GC разделяет всю память на блоки (spans), которые сканируются целиком. Это ускоряет работу в случаях, когда приложение выделяет много относительно маленьких объектов. При этом освобождение памяти происходит не сразу после того, как объект внутри span помечается неиспользуемым, а когда таких объектов накапливается несколько.

Бенчмарки показывают прирост производительности в диапазоне 10–40% в зависимости от нагрузки.

Отключить новый GC можно с помощью специального флага:

```bash
GOEXPERIMENT=nogreenteagc
```

Этот флаг временный — в Go 1.27 его планируют удалить. 

Жми сюда!

Экспериментальный пакет simd/archsimd для работы с SIMD 

changelog

В 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 по одному. В итоге получаем повышение производительности.

Рекурсивные constraints в generics

github issue

В 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

Теперь же код успешно компилируется. Небольшое, но приятное развитие возможностей дженериков.

signal.NotifyContext возвращает причину отмены

changelog

В 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)
}

К сожалению, отличить её как-то иначе, кроме как по тексту сообщения, сейчас нельзя, так как тип приватный и не содержит специальных маркирующих методов.

fmt.Errorf требует меньше ресурсов

changelog

Традиционно считается, что если хотите вернуть просто текстовую ошибку без интерполяции значений, стоит использовать errors.New, а не fmt.Errorf. То есть:

errors.New("some error")

вместо

fmt.Errorf("some error")

так как последняя требует больше ресурсов.

Это не всегда удобно — приходится помнить, какую функцию вызывать в зависимости от аргументов.

В Go 1.26 добавили оптимизацию: теперь fmt.Errorf создаёт меньше аллокаций, и его использование с константной строкой стало практически эквивалентно errors.New.

Отправка логов в несколько обработчиков

changelog

В пакете 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

changelog

Команда 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-канал, там интересно!