У 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 даёт вторую половину сравнения.

