📎 sync.Pool в Go, но с дженериками и типобезопасностью — 24 мая 2026 г. в 15:58:14.623
📎 sync.Pool в Go, но с дженериками и типобезопасностью В стандартной библиотеке Go есть sync.Pool. Штука простая по идее, но полезная на практике. Она позволяет переиспользовать объекты вместо того, чтобы каждый раз выделять под них новую память. Меньше аллокаций, меньше нагрузка на GC, выше производительность. Как это работает Представьте скоростной принтер, который печатает 100 страниц в минуту. Было бы странно, если бы он перед каждой страницей бегал на склад за новой пачкой бумаги. Вместо этого у него есть лоток на 100 листов. Берём лист, печатаем, кладём обратно. sync.Pool работает по тому же принципу. Вот как это выглядит в коде: var paperPool = sync.Pool{ New: func() interface{} { return new(Paper) }, } func printPage() { page := paperPool.Get().(*Paper) page.Reset() defer paperPool.Put(page) page.Print() } Несколько важных моментов про sync.Pool. У пула нет фиксированного размера. Вы можете класть и забирать объекты без жёстких ограничений. Но объект, который вы вернули в пул, может быть в любой момент удалён сборщиком мусора. Не стоит рассчитывать, что он там останется навсегда. Ещё один момент. Объекты могут хранить состояние от предыдущего использования. Поэтому перед использованием или перед возвратом в пул их нужно сбрасывать. Если этого не делать, можно получить трудноуловимые баги с «грязными» данными. Типобезопасная обёртка sync.Pool хранит и возвращает interface{}. Это гибко, но не безопасно. Ничто не мешает положить в пул строку, а достать и попытаться привести к структуре. Приложение упадёт с паникой. Дженерики в Go позволяют обернуть sync.Pool так, чтобы тип контролировался на этапе компиляции. type Pool[T any] struct { internal sync.Pool } func NewPool[T any](newF func() T) *Pool[T] { return &Pool[T]{ internal: sync.Pool{ New: func() interface{} { return newF() }, }, } } Мы создали обёртку, привязанную к конкретному типу T. Под капотом всё тот же sync.Pool с interface{}, но снаружи пользователь работает только с типом T. Теперь методы Get и Put: func (p *Pool[T]) Get() T { return p.internal.Get().(T) } func (p *Pool[T]) Put(x T) { p.internal.Put(x) } В Get мы приводим interface{} к типу T без проверки ошибки. И это нормально. Наша обёртка гарантирует, что в пул попадают только значения типа T. Функция New создаёт T, метод Put принимает только T. Поэтому приведение типа p.internal.Get().(T) не вызовет панику при штатном использовании. Код остаётся чистым, типобезопасным и не требует ручных проверок на каждый Get. Обёртка с дженериками убирает необходимость в ручном приведении типов и снижает риск ошибок. Решение занимает десяток строк, при этом полностью совместимо со стандартным sync.Pool.

