TGViewer
Channel Public Channel
Библиотека Go-разработчика | Golang

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

@goproglib

Все самое полезное для Go-разработчика в одном канале.

Учиться у нас: clc.to/qaSdww

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

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

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

Заявка: 8137752389
Subscribers
24.1K
Photos
2.8K
Videos
54
Links
5.4K

Showing posts older than #7192 · Back to latest

Older Posts 20 shown
Post #7191 2.71K
⏰ Пакет time в Go: таймеры, тикеры и паттерны

Стандартный time закрывает почти все задачи по работе со временем без сторонних библиотек. Разберём основные примитивы и паттерны.

Таймеры

time.NewTimer() срабатывает один раз через заданный интервал:
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("Timer fired!")


Таймер можно остановить до срабатывания через timer.Stop() и перезапустить через timer.Reset().

time.After() — сокращение без доступа к таймеру. time.AfterFunc() вызывает функцию в отдельной горутине.

Тикеры

time.NewTicker() срабатывает повторно через равные интервалы. Всегда вызывайте Stop(), иначе горутина утечёт:
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()

for i := 0; i < 5; i++ {
<-ticker.C
fmt.Println("Tick", i)
}


Таймауты через context

В продакшене таймауты реализуют через context — это позволяет пробросить отмену по всей цепочке вызовов:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

select {
case result := <-doWork(ctx):
fmt.Println("Result:", result)
case <-ctx.Done():
fmt.Println("Timeout:", ctx.Err())
}


Замер времени выполнения

Простая обёртка через defer. Первый вызов запоминает старт, возвращённое замыкание откладывается и печатает разницу:
func measureTime(name string) func() {
start := time.Now()
return func() {
fmt.Printf("%s took %v\n", name, time.Since(start))
}
}

defer measureTime("main")()


Rate limiter

Простейший вариант на time.Tick() — один запрос в секунду:
limiter := time.Tick(time.Second)
for req := range requests {
<-limiter
process(req)
}


Retry с экспоненциальным backoff

Пауза удваивается после каждой неудачи, но не превышает maxBackoff. В продакшене стоит добавить jitter, чтобы клиенты не нагружали сервер одновременно:
backoff := 100 * time.Millisecond
maxBackoff := 10 * time.Second

for i := 0; i < maxRetries; i++ {
if err := fn(); err == nil {
return nil
}
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}


Если переходите с других языков — запомните референсную дату 1/2 3:4:5 2006 и не забывайте вызывать ticker.Stop().

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

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

#GoDeep
  • ❤ 7
  • 👾 1
Post #7189 2.96K
🥶 Go 1.27 уже в release freeze

Команда Go объявила о начале заморозки релиза Go 1.27. С этого момента новые фичи и оптимизации не принимаются до следующего цикла. Багфиксы по-прежнему можно отправлять.

Release Candidate 1 запланирован на вторую неделю июня. Сейчас основной приоритет — закрытие issues, блокирующих RC 1.

➡️ Источник

У команды Go есть новости, а у нас есть новостная рассылка

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

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

#GoLive
  • ❤ 5
  • 👍 2
Post #7188 2.58K
😉 Тесты для конкурентного кода

Каждый, кто писал тесты для конкурентного Go, знает эту боль. Нужно проверить, что что-то произошло (или не произошло) после работы горутины. А единственный способ это сделать — воткнуть time.Sleep и надеяться, что на CI этого хватит.

Выглядело это примерно так:
func TestTimeout(t *testing.T) {
// ... настраиваем логику таймаута ...

time.Sleep(10 * time.Millisecond) // авось хватит

if !timedOut {
t.Fatal("expected timeout, got none")
}
}


10 миллисекунд на один тест — мелочь. Но когда таких тестов сотни, набегают секунды пустого ожидания. А на нагруженном CI раннере 10ms иногда недостаточно, и тест становится flaky.

Что делает synctest

Пакет testing/synctest появился экспериментально в Go 1.24 за флагом GOEXPERIMENT=synctest. В Go 1.25 он вышел в GA с обновлённым API — флаг больше не нужен. Вместо старого synctest.Run используем synctest.Test. Старый API удалён в Go 1.26.

Пакет решает проблему двумя механизмами.

Bubble. synctest.Test() оборачивает тест в изолированную среду. Все горутины, запущенные внутри, принадлежат этому «пузырю».

Виртуальное время. Внутри пузыря time.Sleep, таймеры и тикеры работают на фейковых часах. Реальное время не идёт. Часы сдвигаются только тогда, когда все горутины в пузыре заблокированы и ждут. Тест с пятисекундным таймаутом выполняется за микросекунды.

`synctest.Wait()` блокирует выполнение, пока все остальные горутины в пузыре не окажутся в состоянии устойчивой блокировки. После возврата из Wait() мы точно знаем, что система обработала всё, что могла.

Вот тот же тест, переписанный на synctest:
import "testing/synctest"

func TestTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(
context.Background(), timeout,
)
defer cancel()

time.Sleep(timeout - time.Nanosecond)
synctest.Wait()
if err := ctx.Err(); err != nil {
t.Fatalf("context expired early: %v", err)
}

time.Sleep(time.Nanosecond)
synctest.Wait()
if ctx.Err() != context.DeadlineExceeded {
t.Fatalf("expected DeadlineExceeded, got %v", ctx.Err())
}
})
}


Никакого реального ожидания. Никаких flaky. Никаких изменений в тестируемом коде.

Что считается «устойчивой блокировкой»

Не все блокирующие операции равны. synctest.Wait() учитывает только те, которые могут быть разблокированы исключительно горутинами внутри пузыря.

Учитываются: отправка и получение из каналов, созданных внутри пузыря, time.Sleep, sync.Cond.Wait, sync.WaitGroup.Wait.

Не учитываются: sync.Mutex (обычно захватывается ненадолго), сетевые вызовы и чтение файлов (их может разблокировать ядро ОС).

Сетевые тесты

Для тестирования сетевого взаимодействия внутри пузыря используем net.Pipe() — это in-memory соединения, которые synctest считает устойчиво блокирующими:
func TestServerHandshake(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
serverConn, clientConn := net.Pipe()
defer serverConn.Close()
defer clientConn.Close()

go runServer(serverConn)
go runClient(clientConn)
synctest.Wait()
// Все горутины заблокированы — хендшейк завершён
})
}


Запуск

На Go 1.25+ никаких дополнительных флагов:
go test ./...


Если вы до сих пор боретесь с flaky-тестами в конкурентном коде, synctest убирает главную причину — гадание на time.Sleep. Тесты становятся быстрыми и детерминированными.

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

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

#GoToProduction
  • 👍 14
  • ❤ 2
Post #7187 2.76K
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24

Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.

Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.

Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.

Как говорят создатели Manus:
“KV-cache hit rate is the single most important metric for a production-stage AI agent.”


🛠 Что внутри методички (комбо из 3 статей + код):
Экономика кэширования — особенности провайдеров и как правильно считать затраты.

Частые анти-паттерны — почему ваш кэш постоянно сбрасывается и вы платите больше.

Кэш в AI-агентах — специфика работы с памятью в автономных системах.


🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.

— Забрать комбо-материалы на GitHub

P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).

🎁 Акция в честь старта продаж!

Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.

👉 Забрать 2 курса по цене 1 и начать обучение
Post #7186 2.89K
🤨 Топ-вакансий для Go-разработчиков за неделю

Senior Golang Developer — офис в Сербии

Senior Golang-разработчик — от 300 000 ₽, удаленно по Москве

Golang-разработчик — до 150 000 ₽, удаленно или гибрид в Москве

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

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

#GoWork
  • 😁 2
  • 🌚 1
Post #7184 2.63K
⏱ Пакет time в Go: длительность, арифметика и таймзоны

Разберём тип Duration, операции сложения и вычитания дат, сравнение и работу с часовыми поясами.

Duration

time.Duration — это промежуток времени в наносекундах. Создаётся через умножение на константы пакета:
d := 5 * time.Second
d := 2*time.Hour + 30*time.Minute
d := time.Duration(500) * time.Millisecond


Можно распарсить из строки:
d, err := time.ParseDuration("1h30m")
d, err := time.ParseDuration("2h45m30s")
d, err := time.ParseDuration("100ms")
d, err := time.ParseDuration("-1h") // отрицательная длительность


У Duration есть методы для конвертации в нужные единицы:
d := 2*time.Hour + 30*time.Minute + 45*time.Second
d.Hours() // 2.5125
d.Minutes() // 150.75
d.Seconds() // 9045
d.Milliseconds() // 9045000
d.String() // 2h30m45s


Доступные константы:
time.Nanosecond  // 1
time.Microsecond // 1000 наносекунд
time.Millisecond // 1000 микросекунд
time.Second // 1000 миллисекунд
time.Minute // 60 секунд
time.Hour // 60 минут


Арифметика времени

Добавить Duration к time.Time:
now := time.Now()
future := now.Add(24 * time.Hour)
future := now.Add(2*time.Hour + 30*time.Minute)


Для календарных операций (где важны границы месяцев и лет) есть AddDate():
nextMonth := now.AddDate(0, 1, 0)  // +1 месяц
nextYear := now.AddDate(1, 0, 0) // +1 год
lastWeek := now.AddDate(0, 0, -7) // -7 дней


Разница между двумя моментами:
diff := future.Sub(now) // возвращает Duration
fmt.Println(diff.Hours())


Два удобных сокращения:
elapsed := time.Since(start)      // то же, что time.Now().Sub(start)
remaining := time.Until(deadline) // то же, что deadline.Sub(time.Now())


Сравнение

Методы Before(), After() и Equal() делают то, что ожидаешь:
t1 := time.Now()
t2 := t1.Add(time.Hour)

t1.Before(t2) // true
t1.After(t2) // false
t1.Equal(t2) // false


Начиная с Go 1.20 появился метод Compare(), который возвращает -1, 0 или 1:
cmp := t1.Compare(t2) // -1 (t1 раньше t2)


Таймзоны

Загрузка таймзоны по имени IANA:
nyc, err := time.LoadLocation("America/New_York")
tokyo, err := time.LoadLocation("Asia/Tokyo")
utc := time.UTC
local := time.Local


Конвертация между зонами:
now := time.Now()
utcTime := now.UTC()
nycTime := now.In(nyc)


Создание времени сразу в нужной зоне:
t := time.Date(2024, 1, 15, 10, 30, 0, 0, nyc)


Получить информацию о текущей зоне:
name, offset := now.Zone()
fmt.Println(name, offset) // EST -18000 (смещение в секундах)


Если нужна зона с фиксированным смещением:
est := time.FixedZone("EST", -5*60*60)


Важный момент: time.LoadLocation() ищет данные IANA в системе. Если на сервере нет файла tzdata, вызов вернёт ошибку. В Go 1.15 появился пакет time/tzdata, который вшивает базу зон прямо в бинарник. Достаточно импорта:
import _ "time/tzdata"


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

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

#GoDeep
  • 👍 11
  • 👏 1
Post #7183 3.04K
🧑‍💻 Вы прошли техсобес. Задачи решены. Оффер получил другой

Это vibe hiring — найм по ощущению, где решение принимается до того, как рекрутер посмотрел на ваши компетенции.

В статье — данные двух крупных исследований 2025–2026 годов, гендерная статистика по фидбеку с интервью и объяснение, почему субъективный найм бьёт по самой компании сильнее, чем по кандидату.

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

🐸 Библиотека Go-разработчика
  • ❤ 3
  • 😢 2
  • 🥱 2
Post #7181 2.8K
⏳ Пакет time в Go: основы, создание и форматирование

Разберём базу: как получить текущее время, как создать конкретную дату и как форматировать и парсить строки. Всё на примерах из стандартной библиотеки, без сторонних зависимостей.

Текущее время и его компоненты

Всё начинается с time.Now(). Этот вызов возвращает структуру time.Time с текущим временем:
now := time.Now()
fmt.Println(now) // 2024-01-15 10:30:00.123456789 -0500 EST


Из неё можно вытащить любой компонент через методы:
now.Year()       // 2024
now.Month() // January
now.Day() // 15
now.Hour() // 10
now.Minute() // 30
now.Second() // 0
now.Weekday() // Monday
now.YearDay() // 15 (день в году)


Для интеграций часто нужны Unix-таймстемпы:
now.Unix()      // 1705329000 (секунды)
now.UnixMilli() // 1705329000123 (миллисекунды)
now.UnixNano() // 1705329000123456789 (наносекунды)


Создание конкретной даты

Если нужно задать дату руками, используйте time.Date():
t := time.Date(2024, time.January, 15, 10, 30, 0, 0, time.UTC)


Аргументы по порядку: год, месяц, день, час, минута, секунда, наносекунда, таймзона.

Из Unix-таймстемпа:
t := time.Unix(1705329000, 0)
t := time.UnixMilli(1705329000123)


Из строки через парсинг:
t, err := time.Parse("2006-01-02", "2024-01-15")
t, err := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z")


Нулевое значение time.Time можно проверить методом IsZero():
var t time.Time
fmt.Println(t.IsZero()) // true


Форматирование и парсинг

Тут Go отличается от большинства языков. Вместо %Y-%m-%d или yyyy-MM-dd используется референсная дата: Mon Jan 2 15:04:05 MST 2006. Запоминается просто: 1/2 3:4:5 2006.

Форматируем в строку:
now.Format("2006-01-02")          // 2024-01-15
now.Format("02/01/2006") // 15/01/2024
now.Format("January 2, 2006") // January 15, 2024
now.Format("15:04:05") // 10:30:00
now.Format("3:04 PM") // 10:30 AM


Есть готовые константы:
now.Format(time.RFC3339)  // 2024-01-15T10:30:00-05:00
now.Format(time.Kitchen) // 10:30AM
now.Format(time.DateOnly) // 2024-01-15 (Go 1.20+)
now.Format(time.TimeOnly) // 10:30:00 (Go 1.20+)


Парсинг работает зеркально: передаёте тот же шаблон и строку:
t, err := time.Parse("2006-01-02", "2024-01-15")
t, err := time.Parse("01/02/2006 3:04 PM", "01/15/2024 10:30 AM")


Если нужно парсить с привязкой к таймзоне, используйте time.ParseInLocation():
loc, _ := time.LoadLocation("America/New_York")
t, err := time.ParseInLocation("2006-01-02 15:04", "2024-01-15 10:30", loc)


Шпаргалка по символам формата

2006 — 4-значный год
06 — 2-значный год
01 — месяц с нулём (01)
1 — месяц без нуля (1)
January — полное название месяца
Jan — сокращённое название
02 — день с нулём
2 — день без нуля
Monday — полное название дня недели
Mon — сокращённое
15 — час в 24-часовом формате
03 — час в 12-часовом с нулём
3 — час в 12-часовом без нуля
04 — минуты
05 — секунды
PM — AM/PM
MST — сокращение таймзоны
-07:00 — смещение
Z07:00 — Z для UTC, смещение для остальных

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

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

#GoDeep
  • 👍 12
  • 🤔 2
  • ❤ 1
  • 😢 1
Post #7180 2.91K
⌨️ Cканер уязвимостей для Go от команды Go

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

Чем полезен

Обычные сканеры зависимостей работают грубо. Они сверяют список пакетов с базой CVE и сообщают обо всём, что нашли. Даже если уязвимая функция в вашем коде вообще не вызывается.

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

Данные об уязвимостях берутся из официальной базы Go. При запросе к базе отправляются только пути модулей, без вашего кода или других данных проекта.

Как использовать

Самый простой сценарий. Переходим в директорию модуля и сканируем все пакеты.
cd my-module
govulncheck ./...


Если уязвимостей нет, govulncheck выведет короткое сообщение. Если есть, покажет описание каждой уязвимости и краткий call stack, чтобы было понятно, как именно ваш код вызывает уязвимую функцию.

Вывод может выглядеть так:
main.go:42:3: mypackage.main calls golang.org/x/text/language.Parse


Чтобы увидеть полный стек вызовов, добавьте флаг:
govulncheck -show traces ./...


Для подробного вывода с прогрессом:
govulncheck -show verbose ./...


Сканирование бинарных файлов

govulncheck умеет проверять не только исходный код, но и скомпилированные бинарники. Для этого используется флаг -mode binary:
govulncheck -mode binary $HOME/go/bin/my-go-program


В этом режиме инструмент анализирует таблицу символов бинарника. Call stack при этом не показывается, потому что для него нужен исходный код.

Есть ещё режим -mode extract. Он извлекает из бинарника минимальную информацию, нужную для анализа, и сохраняет её в отдельный файл. Этот файл обычно гораздо меньше оригинального бинарника, и его тоже можно передать в govulncheck с флагом -mode binary.

Форматы вывода

Помимо текстового вывода, govulncheck поддерживает несколько машиночитаемых форматов. JSON для потоковой обработки, SARIF для интеграции с CI/CD и инструментами анализа, VEX (Vulnerability EXchange) по спецификации OpenVEX.

Что стоит учитывать

Инструмент анализирует вызовы через указатели на функции и интерфейсы консервативно. Это иногда приводит к ложным срабатываниям. Вызовы через пакет reflect статическому анализу не видны, поэтому уязвимости, достижимые только через рефлексию, в отчёт не попадут. То же касается unsafe.

На момент версии v1.1.4 нет встроенного механизма подавления конкретных находок. Задача на это ведётся в https://go.dev/issue/61211.

govulncheck полезен тем, что даёт конкретику. Вместо списка «у вас есть пакет с CVE» он показывает, вызывается ли уязвимая функция в вашем коде. Встраивается в CI, понимает бинарники, выводит в SARIF и JSON. Официальный инструмент, поддерживается командой Go.

Знаете где нет уязвимостей? ➡️ В нашей новостной рассылке

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

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

#GoToProduction
  • 👍 7
  • ❤ 4
  • 🔥 4
Post #7179 2.83K
⏳ time.Sleep игнорирует context — пишем свою версию, которая не игнорирует

Стандартный time.Sleep() в Go ничего не знает про контекст. Если вы поставили паузу на 30 секунд, а контекст отменился через 2 — функция всё равно будет ждать все 30. Это проблема в любом коде, где важна отзывчивость на отмену: фоновые воркеры, периодические задачи, graceful shutdown.

Решение — написать свою функцию Sleep, которая завершится досрочно, если контекст будет отменён.

Реализация

Создаём таймер и ждём, что произойдёт раньше: таймер сработает или контекст отменится:
func Sleep(ctx context.Context, d time.Duration) {
timer := time.NewTimer(d)
defer timer.Stop()

select {
case <-ctx.Done():
return
case <-timer.C:
return
}
}


select блокирует горутину до тех пор, пока один из каналов не станет готов. Если контекст отменяется раньше таймера — выходим сразу. Если таймер срабатывает первым — выходим как обычный Sleep. В обоих случаях defer timer.Stop() корректно освобождает ресурсы.

Пример использования

Допустим, есть фоновая задача, которая выполняется в цикле с паузой между итерациями:
func Job(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
doWork()
Sleep(ctx, 10 * time.Second)
}
}
}


Когда контекст отменится, Sleep завершится мгновенно. На следующей итерации цикл проверит ctx.Done() и выйдет. Без нашей функции воркер бы завис в time.Sleep на оставшееся время, а сервис не мог бы корректно остановиться.

Почему функция не возвращает ошибку

Первый вариант напрашивается сам собой — вернуть ctx.Err() при отмене контекста:
func Sleep(ctx context.Context, d time.Duration) error {
timer := time.NewTimer(d)
defer timer.Stop()

select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}


Но на практике это усложняет вызывающий код без пользы. Отмена контекста — это не ошибка самого Sleep. Это сигнал для всей цепочки выполнения.

Код после Sleep обычно тоже проверяет контекст, и если он отменён — тоже завершается. Обрабатывать ошибку именно от Sleep нет смысла, поэтому сигнатура без error делает функцию проще в использовании.

Когда это пригодится

Функция полезна везде, где time.Sleep стоит внутри цикла или последовательности операций, привязанных к контексту. Фоновые джобы, ретраи с задержкой, периодические опросы, rate limiting — всё это становится отзывчивее к отмене без лишнего кода.

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

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

#GoDeep
  • ❤ 12
  • ⚡ 1
Post #7177 2.81K
⚙️ Генерируем типобезопасный код из SQL

Работа с базой данных в Go часто превращается в рутину. Мы пишем SQL-запрос, потом вручную описываем структуру, потом руками сканируем строки в эту структуру. Любая опечатка в имени колонки или несовпадение типов всплывёт только в рантайме.

sqlc подходит к этому иначе. Вы пишете обычный SQL, а sqlc генерирует из него Go-код с типобезопасными интерфейсами. Никаких тегов, никакой рефлексии, никакого рантайм-парсинга. Ошибки ловятся на этапе компиляции.

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

Принцип простой. Вы описываете схему базы, пишете SQL-запросы с аннотациями, запускаете sqlc generate. На выходе получаете Go-пакет с готовыми функциями, структурами и интерфейсами.

Для начала нужен конфиг sqlc.yaml:
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "db"
out: "db"
sql_package: "pgx/v5"


Дальше описываем схему в schema.sql:
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);


И пишем запросы в query.sql. Каждый запрос получает имя и тип результата через аннотацию:
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;

-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;

-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
) RETURNING *;

-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;


Аннотация :one означает, что запрос вернёт одну строку. :many — слайс. :exec — ничего не возвращает. Есть ещё :execresult для случаев, когда нужен sql.Result.

После запуска sqlc generate в папке db/ появятся три файла: models.go со структурами, db.go с интерфейсом подключения, query.sql.go с типобезопасными функциями для каждого запроса.

Использование в коде выглядит так:
queries := db.New(conn)

author, err := queries.CreateAuthor(ctx, db.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: pgtype.Text{String: "Author of The C Programming Language", Valid: true},
})

authors, err := queries.ListAuthors(ctx)


Никаких строковых имён колонок. Если вы переименуете поле в схеме и забудете обновить запрос, sqlc generate упадёт с ошибкой. Если типы не совпадут — тоже.

Что поддерживается

sqlc работает с PostgreSQL, MySQL и SQLite. Генерация кода доступна для Go, Python, Kotlin и TypeScript. Для Go есть поддержка database/sql, pgx/v4 и pgx/v5. Дополнительные языки подключаются через плагины.

Зачем это нужно

sqlc убирает ручной маппинг между SQL и кодом. Вы полностью контролируете свои запросы, при этом получаете строгую типизацию на этапе компиляции. Сгенерированный код читаемый — это обычный Go, который вы бы написали сами, только без ошибок при сканировании строк.

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

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

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

#GoToProduction
  • 👍 10
  • ❤ 3
  • 🥰 2
  • 👏 2
Post #7176 2.92K
💡 Дженерики в Go. Полезные функции и стандартная библиотека

Посмотрим на практические функции, которые упрощают повседневную работу со слайсами и мапами. А также на то, что Go уже включает из коробки в пакетах slices и maps.

Map, Filter, Reduce

Go не предоставляет эти функции в стандартной библиотеке, но с дженериками их легко написать самостоятельно.

Map трансформирует каждый элемент слайса:
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}


Filter оставляет только элементы, подходящие под условие:
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}


Reduce сворачивает слайс в одно значение:
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
result := initial
for _, v := range slice {
result = fn(result, v)
}
return result
}


Пример использования всех трёх вместе:
numbers := []int{1, 2, 3, 4, 5}

doubled := Map(numbers, func(n int) int { return n * 2 })
// [2, 4, 6, 8, 10]

evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
// [2, 4]

sum := Reduce(numbers, 0, func(acc, n int) int { return acc + n })
// 15


Keys и Values для map

Ещё одна частая задача, получить ключи или значения мапы отдельным слайсом:
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}


Аналогично для значений. Порядок не гарантирован, как и при обычной итерации по map в Go.

Дженерик-кеш

Потокобезопасный кеш на дженериках. Полезная штука для сервисов, где нужно кешировать объекты разных типов:
type Cache[K comparable, V any] struct {
mu sync.RWMutex
items map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{items: make(map[K]V)}
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.items[key]
return val, ok
}

func (c *Cache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}


Вместо map[string]interface{} с приведением типов получаем типобезопасный контейнер. Компилятор ловит ошибки за вас.

Пакеты slices и maps

Начиная с Go 1.21, стандартная библиотека включает пакеты slices и maps с готовыми дженерик-функциями.

Прежде чем писать свои утилиты, стоит заглянуть сюда:
import (
"slices"
"maps"
)

numbers := []int{3, 1, 4, 1, 5, 9}

slices.Sort(numbers)
slices.Contains(numbers, 4) // true
slices.Index(numbers, 4) // позиция элемента
slices.Max(numbers) // 9
slices.Min(numbers) // 1
slices.Reverse(numbers)


Для сортировки с кастомной функцией сравнения есть slices.SortFunc:
slices.SortFunc(numbers, func(a, b int) int {
return b - a // по убыванию
})


Пакет maps предлагает Clone, Equal и DeleteFunc:
m := map[string]int{"a": 1, "b": 2}
copy := maps.Clone(m)
maps.DeleteFunc(m, func(k string, v int) bool {
return v < 2
})


Для получения ключей и значений в виде слайсов используется связка maps.Keys / maps.Values с slices.Collect:
keys := slices.Collect(maps.Keys(m))
vals := slices.Collect(maps.Values(m))


Практические советы

Используйте дженерики, когда у вас действительно есть дублирование кода для разных типов. Если функция работает только с одним типом, дженерики не нужны.

Выбирайте максимально точный constraint. comparable вместо any, если нужно сравнение. cmp.Ordered вместо comparable, если нужны операторы < и >.

Дженерики в Go не пытаются заменить всю систему типов. Это точечный инструмент для устранения дублирования.

Вместе с пакетами slices и maps они закрывают большинство задач, которые раньше решались через interface{}, рефлексию или генерацию кода.

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

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

#GoDeep
  • 👍 13
  • ❤ 2
Post #7175 3.31K
📰 Дайджест недели

— Пре-анонс security-фиксов в Go

Патчи выйдут 21 мая. Конкретика пока не раскрыта, известны только номера CVE.

— Как закрыть дыру в контейнере

— fsnotify напугала сообщество

— ACME-клиент на Go

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

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

#GoLive
  • 👾 1
Post #7173 3.3K
🧑‍💻 ACME-клиент на Go с полностью переработанным CLI

Если вы когда-нибудь автоматизировали получение TLS-сертификатов через Let's Encrypt или другой ACME-совместимый CA, то наверняка сталкивались с lego.

lego — это ACME-клиент и библиотека на Go, которая поддерживает более 210 DNS-провайдеров и работает на всех основных платформах. Недавно вышел апдейт, давайте глянем что там.

Одна команда вместо двух

Главное изменение в CLI: команды run и renew объединены в одну lego run. Она сама понимает, нужно получить новый сертификат или продлить существующий. Флаги переехали с глобального уровня на уровень команды.

Было в v4:
lego --dns cloudflare -d '*.example.com' -d example.com run


Стало в v5:
lego run --dns cloudflare -d '*.example.com' -d example.com


Конфигурационный файл

Теперь можно описать всё в .lego.yml и не городить длинные команды в кроне или CI. Сертификаты, челленджи, аккаунты, хуки, логирование — всё в одном файле.

Есть JSON Schema для валидации:
challenges:
cf:
dns:
provider: cloudflare

certificates:
my-cert:
challenge: cf
domains:
- example.com
- '*.example.com'


После этого достаточно передать креды через переменные окружения (или dotenv) и запустить:
CLOUDFLARE_EMAIL="you@example.com" \
CLOUDFLARE_API_KEY="yourkey" \
lego


Управление аккаунтами, сертификатами и архивами

В v5 появились отдельные подкоманды для работы с данными lego.

Аккаунты: lego accounts register, lego accounts recover, lego accounts keyrollover, lego accounts list.

Сертификаты: lego certificates list (статус и дата истечения), lego certificates revoke.

Архивы: lego archives list, lego archives restore.

Хуки жизненного цикла

Можно повесить скрипты на три этапа выпуска сертификата. pre-hook срабатывает до получения или продления (только если реально что-то произойдёт). deploy-hook — после успешного выпуска. post-hook — после завершения операции в любом случае.
lego run -d 'example.com' --deploy-hook='./my-deploy-hook.sh'


Или через конфиг:
hooks:
pre:
command: './my-pre-hook.sh'
deploy:
command: './my-deploy-hook.sh'
post:
command: './my-post-hook.sh'


Хуки получают контекст через переменные окружения: LEGO_HOOK_CERT_PATH, LEGO_HOOK_CERT_KEY_PATH и другие.

Что ещё нового

Поддержка IPv6-only хостов через флаг --ipv6only. Структурированное логирование в форматах text, colored и json. Короткие имена CA-серверов вместо полных URL:
lego run --server='letsencrypt-staging' ...
lego run --server='zerossl' ...
lego run --server='googletrust' ...


Добавлено 24 новых DNS-провайдера, общее число перевалило за 210. EAB-креденшелы теперь нужны только при первой регистрации аккаунта, а не при каждом продлении.

Миграция с v4

В v5 есть ломающие изменения в CLI, структуре директорий и API библиотеки. Перед использованием нужно выполнить:
lego migrate


Эта команда перенесёт хранилище на новый формат. Полное руководство по миграции есть в документации проекта.

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

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

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

#GoLive
  • ❤ 7
  • 👍 4
  • 🔥 3
  • ⚡ 1
  • 🌚 1
Post #7172 3.3K
📎 Ещё один способ вызывать C из Go без Cgo

Мы недавно писали про purego, который позволяет вызывать C-функции из Go без Cgo. speedboost решает ту же задачу, но заходит с другой стороны.

Авторы заявляют меньший оверхед на вызов по сравнению с purego и более низкоуровневый контроль над тем, как именно происходит взаимодействие с shared-библиотекой.

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

API состоит из четырёх шагов. Вы загружаете shared-библиотеку, получаете указатель на символ, описываете сигнатуру функции через дескрипторы типов и вызываете её.

Важная особенность — библиотеку можно встроить прямо в Go-бинарник через //go:embed. Это значит, что .so / .dylib / .dll файл не нужно таскать отдельно, он будет внутри вашего приложения.

Вот пример. Допустим, у нас есть функция multiply, написанная на Zig и скомпилированная в shared-библиотеку.
const std = @import("std");

export fn multiply(a: f64, b: f64) callconv(.c) f64 {
return a * b;
}


Go-код, который её вызывает:
//go:embed lib/zig-out/mymath.shared
var sharedLibrary []byte

func main() {
lib, err := sb.LoadLibrary(sharedLibrary)
if err != nil {
panic(err)
}
defer lib.Unload()

mulPtr := lib.GetSymbol("multiply")

cif := sb.SetFuncSignature(
sb.DoubleTypeDescriptor,
sb.DoubleTypeDescriptor,
sb.DoubleTypeDescriptor,
)

var result float64
err = sb.CallFunction(cif, mulPtr, Ptr(&result),
Ptr(new(40.0)), Ptr(new(2.0)))

fmt.Printf("multiply(40.0, 2.0) = %f\n", result) // 80.0
}


Отличия от purego

В purego вы объявляете Go-функцию и привязываете её к C-символу через RegisterLibFunc. Это удобный и высокоуровневый подход. В speedboost всё явнее. Вы сами описываете сигнатуру через дескрипторы типов (DoubleTypeDescriptor и т.п.) и передаёте аргументы как unsafe.Pointer. Больше контроля, но больше ручной работы.

Авторы speedboost утверждают, что их подход даёт меньший оверхед на каждый FFI-вызов. Независимых бенчмарков пока нет, поэтому делать выводы о производительности стоит осторожно.

Кросс-компиляция

Как и purego, speedboost работает с CGO_ENABLED=0. Для сборки под другую платформу достаточно указать GOOS.
GOOS=linux go generate ./...
GOOS=linux CGO_ENABLED=0 go run .


В примере из репозитория shared-библиотека собирается на Zig, который сам по себе хорош для кросс-компиляции. Но speedboost не привязан к Zig — подойдёт любая библиотека с C ABI, собранная на C, Rust или чём угодно ещё.

Стоит ли использовать

Если вам интересен FFI в Go без Cgo и вы хотите попробовать альтернативу purego с более низкоуровневым API, speedboost стоит посмотреть. Особенно если в вашем сценарии критичен оверхед на каждый вызов.

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

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

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

#GoToProduction
  • 👍 6
  • ❤ 2
  • 👏 1
  • 💯 1
Post #7171 3.41K
✏️ Разбить строку корректно

Допустим, у вас есть строка байтов:
data := []byte("foo,,bar,,,baz")


Вы хотите разбить её по запятой. Но между элементами — где одна, а где три запятые.

Как это сделать правильно? Ответ смотрите в нашем канале с вопросами с собесов

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

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

#ReadySetGo
  • 👍 1
Post #7170 3.39K
💡 Дженерики в Go. Дженерик-типы

Посмотрим, как применять типовые параметры к структурам. На практике это позволяет создавать переиспользуемые контейнеры и паттерны, знакомые по Rust и другим языкам.

➡️ Дженерик-стек

Классический пример. Стек, который работает с любым типом данных:
type Stack[T any] struct {
items []T
}

func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}


Обратите внимание на паттерн var zero T. В Go нельзя вернуть nil для произвольного типа, поэтому создаётся нулевое значение через объявление переменной.

Использование выглядит так:
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
val, _ := intStack.Pop() // 2


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

Pair

Ещё один частый паттерн. Пара из двух значений разных типов:
type Pair[T, U any] struct {
First T
Second U
}

func NewPair[T, U any](first T, second U) Pair[T, U] {
return Pair[T, U]{First: first, Second: second}
}


Удобно, когда нужно вернуть из функции два связанных значения, не создавая под это отдельную структуру.

➡️ Result

Паттерн из Rust. Вместо пары (value, error) используется единый тип, который содержит либо значение, либо ошибку:
type Result[T any] struct {
value T
err error
}

func Ok[T any](value T) Result[T] {
return Result[T]{value: value}
}

func Err[T any](err error) Result[T] {
return Result[T]{err: err}
}

func (r Result[T]) Unwrap() T {
if r.err != nil {
panic(r.err)
}
return r.value
}

func (r Result[T]) UnwrapOr(defaultValue T) T {
if r.err != nil {
return defaultValue
}
return r.value
}


На практике это может выглядеть так:
func divide(a, b float64) Result[float64] {
if b == 0 {
return Err[float64](errors.New("division by zero"))
}
return Ok(a / b)
}

result := divide(10, 2)
fmt.Println(result.Unwrap()) // 5


Стоит ли использовать Result вместо обычного (T, error)? Для большинства Go проектов привычные множественные возвраты по-прежнему предпочтительнее.

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

➡️ Optional

Похожий паттерн, но для случая «значение есть или его нет».

В Go это обычно решается через указатели, но Optional делает намерение явным:
type Optional[T any] struct {
value *T
}

func Some[T any](value T) Optional[T] {
return Optional[T]{value: &value}
}

func None[T any]() Optional[T] {
return Optional[T]{value: nil}
}

func (o Optional[T]) UnwrapOr(defaultValue T) T {
if o.value == nil {
return defaultValue
}
return *o.value
}


Как и с Result, это не замена идиоматичному Go, а дополнительный инструмент для случаев, когда нужна явная семантика «пусто/не пусто».

Дженерик-типы позволяют создавать универсальные контейнеры и паттерны без потери типобезопасности.

Стек, пара, Result, Optional — всё это переиспользуемые строительные блоки, которые раньше приходилось реализовывать через interface{}.

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

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

#GoDeep
  • ❤ 12
  • 👍 7
  • 🔥 1
Post #7169 3.58K
🤨 Go-библиотека fsnotify напугала сообщество

У популярной Go-библиотеки fsnotify сменились доступы к репозиторию, и open source-сообщество забеспокоилось.

Библиотеку используют более 321 000 проектов на GitHub, она лежит в основе множества CLI-инструментов, dev-серверов и инфраструктурных пайплайнов. Когда непонятно, кто именно может пушить код в такую зависимость, последствия чувствуют все.

Go-разработчик Yasuhiro Matsumoto (mattn) написал в X, что его удалили из GitHub-организации fsnotify без объяснений. Пост был на японском, позже его удалили, но к тому моменту сообщество уже забило тревогу.

Выглядело это как классический набор признаков supply chain компрометации: изменение доступов, свежие релизы, удалённый публичный.

Мейнтейнер проекта Martin Tournoij ответил в GitHub-тикете. По его словам, удалённые аккаунты имели коммит-права по историческим причинам, но никогда не были полноценными мейнтейнерами.

Он объяснил, что изменения мержились слишком быстро, без должного ревью на всех поддерживаемых платформах. Ещё одной причиной стало то, что Matsumoto закоммитил напрямую в main без обсуждения. Matsumoto позже признал это ошибкой и извинился.

Реакция экосистемы

Ситуация быстро дошла до крупных проектов. В Kubernetes завели отдельный тикет с вопросом о здоровье fsnotify и предложением рассмотреть форки.

Инженер Docker Sebastiaan van Stijn отметил, что такие библиотеки сидят настолько глубоко в стеке, что о них забывают, а инструменты вроде Dependabot обновляют зависимости почти без контроля.

Аналитики Socket.dev подчеркнули важный момент: на ранних стадиях supply chain атака и обычный конфликт мейнтейнеров выглядят одинаково.

Смена доступов, неожиданные релизы, противоречивые публичные заявления. История с бэкдором в xz-utils всё ещё свежа в памяти, поэтому реакция сообщества была предсказуемо резкой.

Вредоносного кода в fsnotify не обнаружено. Но сама ситуация показательна. Если вы используете эту библиотеку, стоит проверить историю релизов и следить за развитием событий.

Мониторинг активности мейнтейнеров в критичных зависимостях, проверка release history при любых спорных ситуациях и готовность переключиться на форк — это то, что сейчас рекомендуют делать всем.

➡️ Источник

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

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

#GoLive
  • ⚡ 4
  • ❤ 3
  • 🔥 1
Post #7168 3.91K
🧑‍💻 Gorilla Mux переоценён. Go справляется сам

gorilla/mux годами был стандартным выбором для HTTP-роутинга в Go. Он добавлял параметры пути, роутинг по методам и поддержку middleware поверх скромного ServeMux. Но Go 1.22 закрыл большинство этих пробелов.

Что изменилось в Go 1.22

Стандартный ServeMux теперь поддерживает методы и параметры пути прямо в паттерне маршрута:
mux := http.NewServeMux()

mux.HandleFunc("GET /deposits/{walletID}", handleGetDeposit)
mux.HandleFunc("POST /deposits", handleCreateDeposit)
mux.HandleFunc("DELETE /deposits/{walletID}", handleDeleteDeposit)


Параметр пути читается через r.PathValue("walletID"). Роутинг по методу встроен. Для большинства REST API, внутренних сервисов и обработчиков вебхуков этого достаточно.

Когда нужна внешняя библиотека

Если реально нужны сложные цепочки middleware, субрутеры или маршруты с регулярными выражениями — возьмите github.com/go-chi/chi/v5. Он легче Gorilla, хорошо компонуется и следует идиомам Go.

Почему не Gorilla

Gorilla Mux поддерживается, но его дни как разумного дефолта прошли. Вы добавляете зависимость ради функций, которые теперь есть в стандартной библиотеке.

Для нового проекта на Go 1.22+: начните с net/http. Если не хватает — возьмите chi. Gorilla Mux нужен только в очень специфических случаях или при поддержке старого кода.

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

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

#GoToProduction
  • 👍 19
  • 👏 5
  • ❤ 2
  • 🤩 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →