Go 1.27 (на момент написания rc3, финал обещают в августе) приносит generic-методы: метод наконец может объявить собственный тип-параметр. Взял golang:1.27rc3 в Docker и проверил, где у фичи границы. reflect новый метод не видит вообще, хотя go/types на том же типе показывает его в method set — метод доступен компилятору и недоступен через рефлексию, поэтому ни один интерфейс им не удовлетворить, а валидаторы и ORM его не заметят. Померил цену: на 200 инстанцирований с запретом инлайна бинарь с методом больше на 2264 байта, из которых 2000 приходятся на длину имён символов, а не на код; по времени разницы нет — это generic-функция с ресивером и тот же gcshape-стенсилинг. А вот что реально заметит прод на апгрейде: GODEBUG=asynctimerchan=1 теперь роняет процесс до main, encoding/json v1 на движке v2 меняет байты вывода на битом UTF-8, json.RawMessage обзавёлся String() и поменял ваши логи. Плюс замеры нового аллокатора: минус четверть времени на new(16 B) и ноль на 128 байтах. Читать дальше →
Время на прочтение4 мин
Охват и читатели10K
У Box[int] два метода. reflect видит один.
type Box[T any] struct{ v T }
func (b Box[T]) Value() T { return b.v }
func (b Box[T]) Map[U any](f func(T) U) Box[U] {
return Box[U]{v: f(b.v)}
}
go: go1.27rc3
chain: value=42
reflect NumMethod: 1
method: Value func(main.Box[int]) int
Метод с собственным тип-параметром, независимым от ресивера, — главное языковое изменение Go 1.27 (пока rc3, ноты в статусе draft, финал обещают в августе). Box[int] теперь превращается в Box[string] прямо в цепочке вызовов, без package-level хелпера. Англоязычные обзоры успели похоронить под этим старый Go; я собрал контейнер с golang:1.27rc3 и проверил фичу за пределами примера из нот.
Пропажа выборочная. Тот же тип глазами компилятора, через go/types:
go/types NewMethodSet len: 2
Map func[U any](f func(int) U) ex/v1/src.Box[U]
Value func() int
Два метода. В method set языка Map есть, а reflect его не находит ни у Box[int], ни у *Box[int]. Код метода исполняется как обычно, недоступен именно рантайм-доступ: API рефлексии не умеет представить generic-значение и инстанцировать его на лету.
Отсюда потолок фичи. Интерфейс с ровно той сигнатурой, которую даёт U = string, не удовлетворяется:
cannot use Box[int]{…} (value of struct type Box[int]) as StringMapper value in variable
declaration: Box[int] does not implement StringMapper (wrong type for method Map)
have Map[U any](func(int) U) Box[U]
want Map(func(int) string) Box[string]
Объявить такой метод внутри интерфейса тоже нельзя: interface method must have no type parameters.
Всё, что ходит по типам рефлексией в рантайме, новых методов не увидит: валидаторы структур, ORM, DI-контейнеры. Инструменты на go/types — линтеры, часть генераторов — увидят спокойно, так что поддержка у разных тулов появится в разное время.
Method value без инстанцирования тоже не соберётся, и компилятор в диагностике называет метод функцией:
./main.go:11:7: cannot use generic function b.Map without instantiation
./main.go:13:7: cannot use generic function Box[int].Map without instantiation
А b.Map[string] и method expression Box[int].Map[string] собираются нормально. Присваивание var fn func(func(int) string) Box[string] = b.Map тоже проходит, но это не заслуга релиза: вывод типов при присваивании работает с 1.21. В 1.27 его расширили на конверсии и композитные литералы — слайс []func([]int) int{first, last} из generic-функций на 1.26 падает с cannot use generic function first without instantiation, на 1.27rc3 собирается молча.
Раз компилятор говорит «функция», интересно, во сколько обходится новый синтаксис. Первый замер я запорол: двести типов одной формы, вызовы заинлайнились и схлопнулись, в бинаре от инстанцирований не осталось ничего. Правильный вариант: двести разных шейпов (разный размер, разная разметка указателей), //go:noinline на трансформации, одинаковый module path, отличается ровно одна строчка объявления.
bin_method: 2780409 bytes
bin_func: 2778145 bytes
go tool nm показывает в обоих бинарях все 400 символов: 200 стенсилов и 200 словарей. Разница 2264 байта, и код тут почти ни при чём — суммарная длина имён main-символов у метода 18402 байта против 16402 у функции. Две тысячи из 2264 это буквально имена: main..dict.Box[int].Map[main.T100] длиннее, чем main..dict.Map[int,main.T100].
По времени разницы нет. С запретом инлайна медиана семи прогонов даёт 13.30 ns/op у метода против 13.75 у функции; если инлайн разрешить, обе версии съедаются целиком, и -gcflags=-m показывает почему:
can inline Box[go.shape.int].MapM[go.shape.string]
can inline MapF[go.shape.int,go.shape.string]
Тот же gcshape-стенсилинг: generic-метод компилируется как generic-функция с ресивером. За две квадратные скобки вы платите длиной имён, а получаете неймспейсинг и цепочку, которая читается слева направо.
Заодно померил обещанное бесплатное ускорение: компилятор зовёт size-специализированные функции аллокации, в нотах заявлено до 30% на объектах меньше 80 байт. Медиана девяти прогонов на Ryzen 7 PRO 8700GE:
Размер | 1.26 | 1.27rc3 |
|---|---|---|
| 13.98 ns | 10.68 ns |
| 17.33 ns | 14.25 ns |
| 19.00 ns | 19.46 ns |
На 16 байтах минус четверть времени, на 128 разницы нет. Заявленные 60 КБ прибавки к бинарю тоже не сошлись: hello-world отличается на 40 КБ от сборки с GOEXPERIMENT=nosizespecializedmalloc, а против 1.26 тот же бинарь стал на 55 КБ меньше.
Апгрейд на 1.27 больнее тронет места, про которые в обзорах строчка мелким шрифтом.
Каналы таймеров стали синхронными ещё в 1.23, а GODEBUG=asynctimerchan=1 возвращал старое буферизованное поведение. В 1.27 переключатель удалён по регламенту GODEBUG, и удалён жёстко: процесс падает до первой строчки main, без варнинга в лог.
fatal error: removed GODEBUG "asynctimerchan" set to old value "1" in environment
Если эта переменная досталась вам по наследству в Dockerfile, systemd-юните или values.yaml, вы это увидите на выкатке.
Дальше encoding/json: пакет v1 теперь работает поверх движка v2, и обещано, что поведение сохранено. Семантика сохранена, байты нет:
1.26: {"k":"A\ufffdB"} 7b 22 6b 22 3a 22 41 5c 75 66 66 66 64 42 22 7d
1.27: {"k":"A�B"} 7b 22 6b 22 3a 22 41 ef bf bd 42 22 7d
Раньше невалидная последовательность экранировалась в \ufffd, теперь уходят сырые байты U+FFFD. То же с ключами map. Распарсится одинаково, а длина тела, его хеш и подпись меняются: golden-тесты, подписанные вебхуки, побайтовое сравнение ответов — если в данные попадает пользовательский мусор, это ваш случай. Аварийный выход — GOEXPERIMENT=nojsonv2. Оттуда же мелочь, которая тихо поменяет логи: json.RawMessage обзавёлся методом String(), %v теперь печатает {"a":1} вместо [123 34 97 34 58 49 125], а nil — null вместо [].
Перед апгрейдом я бы грепнул инфраструктуру на asynctimerchan, а тесты на побайтовые сравнения JSON. Generic-методы в этом списке последние: они добавляют возможность, поведение уже написанного кода апгрейд оставляет как было.
Тулчейн ставить не надо:
docker run --rm -v "$PWD":/w -w /w -u "$(id -u):$(id -g)" \
-e HOME=/w -e GOCACHE=/w/.cache golang:1.27rc3 go test -bench . -benchmem
Замена образа на golang:1.26 даёт вторую половину сравнения.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Go 1.27 подменил движок encoding/json. Замерил три конфигурации и нашёл, где стало хуже | 0 | 10 | 03-09-2026 |
| 2 | Утёкшие горутины теперь ищет сборщик мусора. Проверил, где он молчит | 0 | 8.33 | 31-08-2026 |
| 3 | [Перевод] Ветвление оказалось дороже лишней работы: как GitHub разогнал обработку исходного кода до 45 ГиБ/с | 0 | 10.36 | 21-08-2026 |
| 4 | Написание ядра ОС с нуля. Часть 3 — Пропуск в высшую лигу | 0 | 8.66 | 23-08-2026 |
| 5 | [Перевод] Rust 1.98.0: алгебраические методы для f32,f64, исправление взаимодействия между ManuallyDrop и Box | 0 | 11.21 | 31-08-2026 |
| 6 | Go SDK для YDB: уменьшаем количество запросов к СУБД для интерактивных транзакций | 0 | 8.8 | 13-08-2026 |
| 7 | Как на ровном месте сэкономить 1000+ ядер, или Куда на самом деле уходили 80% CPU | 0 | 9.12 | 08-09-2026 |
| 8 | Моя собственная Gated RNN: как работает? (и бенчмарки, конечно же) | 0 | 5.62 | 26-09-2026 |
| 9 | Миллион контейнеров и быстрый откат релиза: эволюция деплоя в RTC | 0 | 7.49 | 18-08-2026 |