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

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

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

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

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

#WXSSA
Download Telegram
🗣 Топ-вакансий для Go-разработчиков за неделю

Backend-разработчик (Go, PostgreSQL) — до 200 000 ₽, удалённо по Москве

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

Senior Golang Developer — до 280 000 ₽, удаленно

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

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

#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM
⭐️ Вызов C-функций из Go без Cgo

Если вы работали с Go и вам нужно было вызвать C-библиотеку, то вы наверняка сталкивались с Cgo.

Cgo работает, но тянет за собой целый набор проблем. Нужен C-компилятор на каждой целевой платформе. Кросс-компиляция превращается в боль. Сборка замедляется. Бинарники раздуваются. purego решает всё это, позволяя вызывать C-функции из чистого Go.

Откуда взялся проект

Библиотека выросла из игрового движка Ebitengine. Его авторы портировали движок на чистый Go для Windows, что позволило кросс-компилировать на Windows с любой ОС одной командой GOOS=windows. purego родился, чтобы принести тот же подход на macOS, Linux и другие платформы.

Что даёт purego

Без Cgo отпадает необходимость в C-компиляторе. Вы можете собирать проект под другую платформу, просто задав GOOS и GOARCH. Сборка кешируется целиком как обычный Go-проект и работает быстрее. Бинарники становятся меньше, потому что Cgo генерирует обёртку на C для каждого вызова, а purego этого не делает.

Ещё purego умеет загружать символы из shared-библиотек в рантайме. Это можно использовать как систему плагинов или для FFI-вызовов в библиотеки на других языках, скомпилированные в .so / .dylib / .dll.

purego работает и при CGO_ENABLED=1. Это значит, что можно портировать проект с Cgo на purego постепенно, не переписывая всё разом.

Как это выглядит в коде

API минимальный. Вы открываете библиотеку через Dlopen, затем регистрируете Go-функцию, которая будет вызывать C:
package main

import (
"fmt"
"runtime"

"github.com/ebitengine/purego"
)

func getSystemLibrary() string {
switch runtime.GOOS {
case "darwin":
return "/usr/lib/libSystem.B.dylib"
case "linux":
return "libc.so.6"
default:
panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
}
}

func main() {
libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
var puts func(string)
purego.RegisterLibFunc(&puts, libc, "puts")
puts("Calling C from Go without Cgo!")
}


Обратите внимание на RegisterLibFunc. Вы объявляете переменную с нужной Go-сигнатурой, а purego привязывает её к C-функции по имени. Никаких // #cgo директив, никаких .h файлов.

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

purego подходит, если вам нужно вызывать C-библиотеку из Go и при этом важна простота сборки и кросс-компиляция. Типичные сценарии — работа с системными библиотеками, графические движки, аудио, нативные SDK.

Если ваш проект уже плотно завязан на Cgo и работает на одной платформе, смысла переезжать может не быть. Но если вы начинаете новый проект или хотите избавиться от зависимости на C-тулчейн, purego стоит попробовать.

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

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

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

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥4🤔1
🧑‍💻 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16👏52🤩1
🤨 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
Please open Telegram to view this post
VIEW IN TELEGRAM
43🔥1
💡 Дженерики в 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
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍5🔥1
✏️ Разбить строку корректно

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


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

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

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

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

#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
📎 Ещё один способ вызывать 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51👏1💯1
🧑‍💻 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
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍3🔥31🌚1
📰 Дайджест недели

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

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

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

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

ACME-клиент на Go

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

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

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👾1
💡 Дженерики в 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
⚙️ Генерируем типобезопасный код из 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏21🥰1
🔮 Асинхронный код часто воспринимается как магия: функции работают, задачи выполняются, но при сбоях или оптимизации становится непонятно, как всё устроено. Без этого сложно писать предсказуемые и эффективные решения.

❗️ На открытом уроке разберём, как Rust представляет асинхронные задачи, как компилятор преобразует async-функции и что происходит во время выполнения. Покажем, как описывать собственные асинхронные задачи и контролировать их поведение.

👣 Урок проходит в преддверии старта курса «Rust-разработчик. Продвинутый уровень». Если вы хотите понимать асинхронность на уровне реализации, а не только использовать её — подключайтесь.

➡️ Встречаемся 19 мая в 20:00 МСК. Подробности и регистрация: https://clc.to/zlHNRg

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
Please open Telegram to view this post
VIEW IN TELEGRAM
👏1🌚1
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
Please open Telegram to view this post
VIEW IN TELEGRAM
12
⌨️ 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍63🔥3
Пакет 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🤔21😢1
🔍Тестовое собеседование с Go ТехЛидом из Wildberries & Russ в этот четверг

21 мая(в четверг!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Go-разработчика.

Как это будет:
📂 Рамиль Мясоутов, ТехЛид из WildBerries, ex-Купер будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Рамиль будет комментировать каждый ответ респондента, чтобы дать понять, чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Рамилю

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Go-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир →
@shortcut_go_bot Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1😁1🥱1