Библиотека Go-разработчика | Golang
24K subscribers
2.54K photos
48 videos
88 files
5.06K links
Все самое полезное для Go-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/32d20779

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4a8c24689c2151c752af0

#WXSSA
Download Telegram
🤩 В международную компанию или на валютную удалёнку

Большинство сеньоров и мидлов знают от силы 3–4 джоб-сайта. На десятках других площадок висят вакансии от стартапов, YC-компаний и распределённых команд с валютными зарплатами и честными условиями.

Собрали 30 платформ с разбором под разные цели — от поиска работы в стартапе до фриланс-заказов и геймдева.

➡️ Смотреть подборку

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика
Please open Telegram to view this post
VIEW IN TELEGRAM
2
🧑‍💻Первые новости апреля

Молниеносная неделя, не так ли?

Neovim 0.12.0

Апдейт Go-обёртки для YottaDB

Go 1.26.2 и 1.25.9

Библиотека для написания LSP-серверов

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🛠 CLI-прокси, который режет токены LLM

Когда Claude Code или любой другой AI-агент запускает go test ./..., он получает обратно сотни строк с путями, покрытием, временем выполнения. Всё это летит в контекстное окно и съедает токены. А нужно агенту одно — прошли тесты или нет.

snip это CLI-прокси, который перехватывает вывод shell-команд до того, как они попадают в контекст LLM, и сжимает их до минимально необходимого.

Что делает snip

Инструмент встаёт между AI-инструментом и шеллом. Когда агент запускает команду, snip перехватывает вывод, прогоняет через YAML-фильтр и отдаёт агенту только суть.

Пример. go test ./... без фильтра отдаёт 689 токенов с путями пакетов, временем и покрытием каждого. С snip агент видит:
10 passed, 0 failed


Как это работает

snip написан на Go, запускается как единый статический бинарник. Фильтры — декларативные YAML-файлы. Это ключевое архитектурное решение: движок и правила фильтрации независимы.

Каждый фильтр описывает, какую команду перехватить, что с её выводом сделать и как отформатировать результат. Пример встроенного фильтра для git log:
name: "git-log"
match:
command: "git"
subcommand: "log"
inject:
args: ["--pretty=format:%h %s (%ar) <%an>", "--no-merges"]
defaults:
"-n": "10"
pipeline:
- action: "keep_lines"
pattern: "\\S"
- action: "truncate_lines"
max: 80
- action: "format_template"
template: "{{.count}} commits:\n{{.lines}}"


Если команда не попадает ни под один фильтр, она проходит насквозь без изменений.

Установка и запуск

Через Homebrew:
brew install edouard-claude/tap/snip


Через Go:
go install github.com/edouard-claude/snip/cmd/snip@latest


Подключение к Claude Code одной командой:
snip init


Это устанавливает PreToolUse хук. Claude Code ничего не замечает — он получает сжатый вывод как будто так и было.

Для Cursor нужно добавить в ~/.cursor/hooks.json:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{ "command": "~/.claude/hooks/snip-rewrite.sh" }
]
}
}


Для других инструментов достаточно алиасов в .bashrc или .zshrc:
alias git="snip git"
alias go="snip go"
alias cargo="snip cargo"


Поддерживаются: git, go, cargo, npm, npx, yarn, pnpm, docker, kubectl, make, pip, pytest, jest, tsc, eslint, rustc.

Свои фильтры:
snip init  # создаёт ~/.config/snip/filters/
vim ~/.config/snip/filters/my-tool.yaml


Пользовательские фильтры приоритетнее встроенных. Писать их может любой, кто знает YAML — не нужно трогать код проекта.

Доступно 16 пайплайн-действий: keep_lines, remove_lines, truncate_lines, strip_ansi, head, tail, group_by, dedup, json_extract, regex_extract, state_machine, aggregate, format_template, compact_path и другие.

Статистика:
snip gain           # дашборд с общей статистикой
snip gain --daily # разбивка по дням
snip gain --top 10 # топ команд по экономии токенов
snip gain --json # вывод в JSON


➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍4
🔄 oaswrap/spec v0.4.0

oaswrap/spec генерирует OpenAPI 3.x спецификацию прямо из Go-кода, без YAML-файлов руками и без аннотаций в комментариях.

5 апреля вышел релиз v0.4.0. Посмотрим что поменялось.

Два новых адаптера

Добавлена поддержка Fiber v3 и Echo v5. Теперь список фреймворков с готовыми адаптерами выглядит так: Chi, Echo, Echo v5, Gin, Fiber, Fiber v3, стандартный net/http, Mux, HTTPRouter.

Если вы уже используете Fiber v3 или Echo v5 и откладывали интеграцию документации именно из-за отсутствия адаптера, теперь повода нет.

Объединение дублирующихся ответов

Раньше, если несколько обработчиков возвращали один и тот же HTTP-статус с разными схемами, в спецификации появлялись дубли. Теперь они автоматически сворачиваются в один oneOf-блок.

Обновлённый Spec UI

Встроенный UI обновился до версии 0.2.0 и получил поддержку кастомных опций. Теперь можно настроить внешний вид документации без правок исходников.

Также починили баг с регистрацией asset-handler при использовании embedded UI.

Минимальный пример для генерации спецификации в файл:
r := spec.NewRouter(
option.WithTitle("My API"),
option.WithVersion("1.0.0"),
)

r.Get("/users/{id}",
option.Summary("Get user"),
option.Request(new(GetUserRequest)),
option.Response(200, new(User)),
)

r.WriteSchemaTo("openapi.yaml")


➡️ Release Notes

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥52
👨‍💻 errgroup: управляем горутинами и ошибками

Стандартный sync.WaitGroup умеет только ждать завершения, про ошибки он ничего не знает. Приходится городить каналы, мьютексы, слайсы ошибок. Всё это работает, но шаблонного кода много, и легко допустить гонку.

errgroup из пакета golang.org/x/sync даёт WaitGroup с поддержкой ошибок и context-отмены из коробки.

Что такое errgroup

errgroup это обёртка над sync.WaitGroup, которая:

• запускает горутины через метод Go
• собирает первую ненулевую ошибку
• при необходимости отменяет контекст, если одна из горутин упала

Пакет не входит в стандартную библиотеку, поэтому нужно поставить его отдельно:
go get golang.org/x/sync/errgroup


Базовый пример:
package main

import (
"fmt"
"golang.org/x/sync/errgroup"
)

func main() {
var g errgroup.Group

g.Go(func() error {
// первая задача
return nil
})

g.Go(func() error {
// вторая задача — допустим, она падает
return fmt.Errorf("что-то пошло не так")
})

if err := g.Wait(); err != nil {
fmt.Println("ошибка:", err)
}
}


g.Wait() ждёт все горутины и возвращает первую ненулевую ошибку. Остальные ошибки теряются. Если нужны все ошибки, придётся собирать их вручную.

С отменой контекста

Если нужно, чтобы при ошибке одной горутины остальные тоже остановились, то используйте errgroup.WithContext:
func fetchData(ctx context.Context, id int) error {
select {
case <-ctx.Done():
fmt.Printf("горутина %d: контекст отменён\n", id)
return ctx.Err()
case <-time.After(time.Duration(id) * time.Second):
if id == 2 {
return fmt.Errorf("горутина %d упала", id)
}
fmt.Printf("горутина %d завершилась\n", id)
return nil
}
}

func main() {
ctx := context.Background()
g, ctx := errgroup.WithContext(ctx)

for i := 1; i <= 4; i++ {
i := i // важно: захват переменной в замыкании
g.Go(func() error {
return fetchData(ctx, i)
})
}

if err := g.Wait(); err != nil {
fmt.Println("итоговая ошибка:", err)
}
}


Когда горутина с id == 2 вернёт ошибку, контекст отменится, и все остальные горутины получат сигнал через ctx.Done(). Они могут среагировать и завершиться досрочно.

Ограничение параллелизма через SetLimit

С Go 1.22 в errgroup появился метод SetLimit. Он задаёт максимальное число одновременно работающих горутин:
var g errgroup.Group
g.SetLimit(5) // не больше 5 горутин одновременно

for _, url := range urls {
url := url
g.Go(func() error {
return fetch(url)
})
}

if err := g.Wait(); err != nil {
fmt.Println(err)
}


Это удобно, когда обрабатываешь большой список задач и не хочешь перегружать систему или внешний сервис.

TryGo: не блокировать, если горутин уже достаточно

TryGo запускает горутину только если не превышен лимит. Если лимит исчерпан возвращает false и ничего не запускает:
g.SetLimit(3)

for _, item := range items {
item := item
if g.TryGo(func() error {
return process(item)
}) {
fmt.Println("запущено:", item)
} else {
fmt.Println("лимит исчерпан, пропускаем:", item)
}
}


Пригодится, если нужно обработать задачу по-другому вместо ожидания свободного слота.

Когда использовать errgroup

errgroup хорошо подходит, когда:

• нужно запустить несколько независимых задач параллельно и дождаться всех
• важно получить хотя бы одну ошибку, а не молча игнорировать
• хочешь остановить остальные горутины при первой ошибке через контекст
• нужно ограничить число параллельных воркеров

Он не подойдёт, если важны все ошибки от всех горутин, тогда придётся собирать их самостоятельно через канал или синхронизированный слайс.

errgroup убирает большую часть шаблонного кода при параллельном выполнении задач. Вместо ручной синхронизации через каналы и мьютексы — чистый и предсказуемый API.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍163
Проект можно запустить быстро, но выдержит ли он рост нагрузки, требования к безопасности и отказоустойчивости? Часто на старте думают только о функциях. Производительность, масштабирование, защита — добавим потом. В реальности это «потом» оборачивается дорогими переделками и компромиссами.

На бесплатном вебинаре:

- разберём, как требования к нагрузке, отказоустойчивости и безопасности формируют архитектуру с первого дня.

- поговорим о том, какие нефункциональные требования влияют на систему сильнее всего.

- как собирать и формулировать их вместе с бизнесом и как принимать архитектурные решения с учётом сроков и бюджета.

Спикер Александр Хохлов — архитектор платформенных решений в ГК Иннотех.

Открытый урок проходит в преддверии старта курса «Проектирование систем».

Регистрируйтесь сейчас - напомним перед вебинаром:
регистрация

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
😁2🥱1
💡 Хватит считать индексы вручную

stringscut — это статический анализатор из пакета golang.org/x/tools/go/analysis. Он находит устаревшие паттерны с strings.Index и предлагает заменить их на strings.Cut, который появился в Go 1.18.

Раньше, чтобы разбить строку по разделителю, писали так:
idx := strings.Index(s, substr)
if idx >= 0 {
return s[:idx]
}


Это работает, но здесь несколько проблем. Нужно держать в голове индекс, вручную считать срезы и помнить, что >= 0 означает «нашли». Это читается медленно и легко ломается при рефакторинге.

Паттерн 1. Разбивка строки по разделителю

До:
idx := strings.Index(s, substr)
if idx >= 0 {
return s[:idx]
}


После:
before, _, ok := strings.Cut(s, substr)
if ok {
return before
}


strings.Cut сразу возвращает часть до разделителя, часть после и булев флаг. Не нужно возиться с индексами.

Паттерн 2. Простая проверка наличия подстроки

До:
idx := strings.Index(s, substr)
if idx >= 0 {
return
}


После:
found := strings.Contains(s, substr)
if found {
return
}


Если индекс нигде не используется, анализатор предлагает strings.Contains. Это честнее читается и сразу видно намерение.

Анализатор обрабатывает не только strings.Index, но и:

strings.IndexByte для поиска по одному байту
• пакет bytes. Те же паттерны, но для []byte

Автоматическая замена предлагается только если между определением и использованием idx, s или substr нет никаких изменений этих переменных. Если где-то в середине idx переопределяется или s мутирует, анализатор промолчит. Это осознанное решение, чтобы не ломать логику.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍3
🔄 Патчи с исправлениями безопасности

Вчера вышли Go 1.26.2 и 1.25.9. Релизы закрывают 10 дыр безопасности.

Ключевые проблемы

В Linux Root.Chmod мог выйти за root по симлинку, если цель меняли в процессе. Html/template неверно отслеживал контекст в JS-литералах с ветками и действиями, открывая XSS. Crypto/x509 игнорировал DNS-констрейнты для wildcard с разным регистром, плюс DoS от кучи intermediates или policy mappings.

сmd/compile пропускал overlap check в no-op конверсиях интерфейсов и bound check для индексов с overflow. Archive/tar жрал память на старом GNU sparse с тонной регионов; ввели лимиты. Crypto/tls 1.3 зависал на нескольких key update в record, DoS.

сmd/go позволял SWIG обходить trust с именами файлов; запретили такие имена.

➡️ Источник

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥42
👀 maps.Equal медленнее reflect.DeepEqual

В Go есть два способа сравнить два map: maps.Equal из стандартной библиотеки и reflect.DeepEqual. Первый появился в Go 1.21 как типобезопасная замена второму. Логично предположить, что он и работает быстрее. Но это не так: по крайней мере в одном конкретном сценарии.

Что показывает бенчмарк:
var m = map[string]int{
"alpha": 1, "beta": 2, "gamma": 3,
"delta": 4, "epsilon": 5, "zeta": 6,
"eta": 7, "theta": 8, "iota": 9,
"kappa": 10,
}

func BenchmarkMapsEqual_SameMap(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = maps.Equal(m, m) // передаём один и тот же map дважды
}
}

func BenchmarkReflectDeepEqual_SameMap(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = reflect.DeepEqual(m, m)
}
}


Результаты на Go 1.25:
BenchmarkMapsEqual_SameMap-12                    4172912               292.3 ns/op             0 B/op          0 allocs/op
BenchmarkReflectDeepEqual_SameMap-12 10414143 111.3 ns/op 0 B/op 0 allocs/op


reflect.DeepEqual быстрее, хотя оба получают один и тот же указатель.

reflect.DeepEqual делает проверку в самом начале: если оба аргумента указывают на один и тот же объект, функция сразу возвращает true. Это классическая оптимизация идентичности: один == по указателю, и готово.

maps.Equal такой проверки не делает. Он всегда итерируется по всем ключам первой мапы и сверяет их значения со второй. Даже если оба аргумента одна и та же мапа.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7👾21
💪 Топ-вакансий для Go-разработчиков за неделю

Middle Go-Developer — удаленно

Junior Golang — 100 000 ₽, гибрид в Москве

Senior Golang Developer — до 450 000 ₽, гибрид в Санкт-Петербурге

Бустер — удалённо по всему миру

➡️ Еще больше топовых вакансий — в нашем канале Go jobs

🐸 Библиотека Go-разработчика

#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM
⚡️ Логирование с уровнями для Go

Когда в проекте на Go растёт количество сервисов или усложняется бизнес-логика, стандартного log пакета начинает не хватать. Нет уровней серьёзности, нет гибкого управления verbosity, нет нормального разделения потоков вывода. Приходится либо городить обёртки, либо тащить тяжёлые зависимости.

glog это портированная на Go реализация логирования из C++ библиотеки glog, которую Google использует внутри. Пакет поддерживает уровни логирования, запись в файлы, управление verbosity через флаги и фильтрацию по файлам.

Что умеет

Четыре уровня серьёзности: Info, Warning, Error, Fatal. Плюс форматированные варианты: Infof, Errorf и так далее. Fatal после вывода сообщения вызывает os.Exit(1).

Отдельно есть V-логирование — способ включать детальные логи только там, где нужно, без изменения кода:
if glog.V(2) {
glog.Info("Starting transaction...")
}

glog.V(2).Infoln("Processed", nItems, "elements")


Флаг -v=2 при запуске включает все вызовы с уровнем 2 и ниже. Если нужно точечно включить логи только в конкретном файле, используется -vmodule:
./myapp -vmodule=service=3,handler=1


Здесь service.go будет логировать на уровне 3, а handler.go на уровне 1.

Как подключить:
go get github.com/golang/glog


Минимальный пример:
package main

import (
"flag"
"github.com/golang/glog"
)

func main() {
flag.Parse()
defer glog.Flush()

glog.Info("Сервис запущен")
glog.Warningf("Свободной памяти мало: %d MB", 128)
glog.Error("Не удалось подключиться к базе")
}


Важный момент: flag.Parse() нужно вызвать до любого лога, иначе флаги не применятся. glog.Flush() в defer гарантирует, что буферизованные записи попадут в файл перед завершением программы.

По умолчанию логи пишутся в файлы во временной директории. Чтобы видеть их в stderr, запускаем с флагом:
./myapp -logtostderr


Или в оба места сразу:
./myapp -alsologtostderr


Если нужна современная замена с поддержкой структуривоанного логирования, стоит смотреть в сторону zap или slog из стандартной библиотеки Go 1.21+. Для задач, где достаточно уровней и файлового вывода без лишних зависимостей, glog справляется хорошо.

➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43
🧑‍💻 Эмулятор AWS-сервисов

Kumo — это небольшой инструмент на Go для локальной имитации AWS-сервисов. Он помогает разработчикам тестировать приложения без облака.

Проект подходит для unit-тестов, где нужен мок для облачных вызовов. Устанавливается через go install github.com/sivchari/kumo@latest. Запускается как прокси, а SDK настраивается на локальный эндпоинт.

Поддерживает базовые операции над S3: создание бакетов, загрузку объектов, список файлов. Для DynamoDB эмулирует таблицы, put/get item. Конфиг через флаги или env.

➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍191
🛠 ECS на Go

Entity Component System (ECS) это архитектурный паттерн, популярный в геймдеве и симуляциях. Идея простая: вместо объектов с данными и поведением используются сущности без данных, компоненты с данными и системы/запросы, которые обрабатывают сущности с нужным набором компонентов.

Ark это архетипная реализация ECS для Go. Архетипный подход означает, что сущности с одинаковым набором компонентов хранятся вместе в памяти, что максимизирует локальность данных при итерации.

Что внутри

Типобезопасный API с дженериками, никакого interface{} и рефлексии в горячих путях. Отношения между сущностями как first-class feature. Система событий с фильтрацией. Batch-операции для массовых изменений. Сериализация мира через ark-serde. Ноль внешних зависимостей, 100% покрытие тестами.

Никаких встроенных систем, только запросы. Структуру приложения выбираем сами, или используем ark-tools со scheduler'ом:
world := ecs.NewWorld()
mapper := ecs.NewMap2[Position, Velocity](world)
filter := ecs.NewFilter2[Position, Velocity](world)

query := filter.Query()
for query.Next() {
pos, vel := query.Get()
pos.X += vel.DX
pos.Y += vel.DY
}


Свежий релиз v0.8.0

7 апреля вышла v0.8.0 с заметными изменениями. Добавлена итерация по таблицам, которая даёт примерно двукратное ускорение по сравнению с обычной итерацией:
for query.NextTable() {
positions, velocities := query.GetColumns()
for i := range positions {
pos, vel := &positions[i], &velocities[i]
pos.X += vel.X
pos.Y += vel.Y
}
}


Также очистка памяти компонентов через memclrNoHeapPointers вместо обнуления вручную, перемещение нетривиальных компонентов стало быстрее на 30% за счёт typedmemmove вместо рефлексии, добавлены регрессионные бенчмарки для сравнения PR с main-веткой.

➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍2
Что выведет код с картинки

strings.ContainsAny проверяет, содержит ли строка хотя бы один символ из набора. Но что происходит, когда оба аргумента пустые строки?

Подсказка: загляни в документацию к функции и обрати внимание на крайние случаи.

Ответ: в нашем канале с задачами

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Компилятор Go строит типы и ловит циклические зависимости

Go 1.26 принёс незаметное, но важное изменение в type checker. Команда Go опубликовала подробный разбор того, как именно компилятор конструирует типы и обнаруживает циклические зависимости.

Как устроена конструкция типов

Когда компилятор встречает объявление типа, он строит внутреннее дерево структур. Для простого случая:
type T []U
type U *int


Всё линейно: сначала строится T, внутри неё []U, внутри неё U, внутри неё *int. Каждый тип становится «завершённым» когда все его поля заполнены и все зависимые типы тоже завершены. Процесс идёт вглубь и разматывается обратно.

Рекурсивные типы

С рекурсией интереснее:
type T []U
type U *T

Здесь T встречается пока ещё строится. Компилятор просто ставит указатель на незавершённый T и движется дальше, рассчитывая что T завершится позже. Когда конструкция доходит до конца, весь "цикл" типов завершается одновременно.

Проблема возникает когда незавершённый тип нужно не просто упомянуть, а заглянуть внутрь:
type T [unsafe.Sizeof(T{})]int


Чтобы вычислить размер массива, нужно знать размер T. Но чтобы знать размер T, нужно завершить построение массива. Круговая зависимость, которую невозможно разрешить, это ошибка цикла.

В Go 1.26 переписали подход: вместо сложной bespoke-логики для каждого случая, компилятор теперь отслеживает неполные значения систематически. В каждом месте где может возникнуть значение неполного типа, вставлена проверка:
if !isComplete(T) {
reportCycleErr(T)
return invalid
}


Это закрыло несколько краевых случаев, которые раньше приводили к панике компилятора вместо нормальной ошибки.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍4