Backend-разработчик (Go, PostgreSQL) — до 200 000 ₽, удалённо по Москве
Golang разработчик (Senior) — от 350 000 ₽, удалённо по Москве
Senior Golang Developer — до 280 000 ₽, удаленно
#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM
Если вы работали с 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 стоит попробовать.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥4🤔1
🧑💻 Gorilla Mux переоценён. Go справляется сам
Что изменилось в Go 1.22
Стандартный
Параметр пути читается через
Когда нужна внешняя библиотека
Если реально нужны сложные цепочки middleware, субрутеры или маршруты с регулярными выражениями — возьмите
Почему не Gorilla
Gorilla Mux поддерживается, но его дни как разумного дефолта прошли. Вы добавляете зависимость ради функций, которые теперь есть в стандартной библиотеке.
Для нового проекта на Go 1.22+: начните с
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction
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 нужен только в очень специфических случаях или при поддержке старого кода.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16👏5❤2🤩1
У популярной 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 при любых спорных ситуациях и готовность переключиться на форк — это то, что сейчас рекомендуют делать всем.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
⚡4❤3🔥1
Посмотрим, как применять типовые параметры к структурам. На практике это позволяет создавать переиспользуемые контейнеры и паттерны, знакомые по 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}
}Удобно, когда нужно вернуть из функции два связанных значения, не создавая под это отдельную структуру.
Паттерн из 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 для значений по умолчанию.Похожий паттерн, но для случая «значение есть или его нет».
В 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{}.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍5🔥1
Допустим, у вас есть строка байтов:
data := []byte("foo,,bar,,,baz")Вы хотите разбить её по запятой. Но между элементами — где одна, а где три запятые.
Как это сделать правильно? Ответ смотрите в нашем канале с вопросами с собесов
📍 Навигация: Вакансии • Задачи • Собесы
#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
Мы недавно писали про
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 стоит посмотреть. Особенно если в вашем сценарии критичен оверхед на каждый вызов.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤1👏1💯1
🧑💻 ACME-клиент на Go с полностью переработанным CLI
Если вы когда-нибудь автоматизировали получение TLS-сертификатов через Let's Encrypt или другой ACME-совместимый CA, то наверняка сталкивались с
Одна команда вместо двух
Главное изменение в CLI: команды
Было в v4:
Стало в v5:
Конфигурационный файл
Теперь можно описать всё в
Есть JSON Schema для валидации:
После этого достаточно передать креды через переменные окружения (или dotenv) и запустить:
Управление аккаунтами, сертификатами и архивами
В v5 появились отдельные подкоманды для работы с данными lego.
Аккаунты:
Сертификаты:
Архивы:
Хуки жизненного цикла
Можно повесить скрипты на три этапа выпуска сертификата.
Или через конфиг:
Хуки получают контекст через переменные окружения:
Что ещё нового
Поддержка IPv6-only хостов через флаг
Добавлено 24 новых DNS-провайдера, общее число перевалило за 210. EAB-креденшелы теперь нужны только при первой регистрации аккаунта, а не при каждом продлении.
Миграция с v4
В v5 есть ломающие изменения в CLI, структуре директорий и API библиотеки. Перед использованием нужно выполнить:
Эта команда перенесёт хранилище на новый формат. Полное руководство по миграции есть в документации проекта.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
Если вы когда-нибудь автоматизировали получение 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
Эта команда перенесёт хранилище на новый формат. Полное руководство по миграции есть в документации проекта.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3🔥3⚡1🌚1
— Пре-анонс security-фиксов в Go
Патчи выйдут 21 мая. Конкретика пока не раскрыта, известны только номера CVE.
— Как закрыть дыру в контейнере
— fsnotify напугала сообщество
— ACME-клиент на Go
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👾1
Посмотрим на практические функции, которые упрощают повседневную работу со слайсами и мапами. А также на то, что 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 })
// 15Keys и 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{}, рефлексию или генерацию кода.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Работа с базой данных в 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, который вы бы написали сами, только без ошибок при сканировании строк.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏2❤1🥰1
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
Please open Telegram to view this post
VIEW IN TELEGRAM
👏1🌚1
⏳ time.Sleep игнорирует context — пишем свою версию, которая не игнорирует
Стандартный
Решение — написать свою функцию
Реализация
Создаём таймер и ждём, что произойдёт раньше: таймер сработает или контекст отменится:
Пример использования
Допустим, есть фоновая задача, которая выполняется в цикле с паузой между итерациями:
Когда контекст отменится,
Почему функция не возвращает ошибку
Первый вариант напрашивается сам собой — вернуть
Но на практике это усложняет вызывающий код без пользы. Отмена контекста — это не ошибка самого
Код после
Когда это пригодится
Функция полезна везде, где
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep
Стандартный
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 — всё это становится отзывчивее к отмене без лишнего кода.📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12
В зависимостях 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.Знаете где нет уязвимостей?
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3
Разберём базу: как получить текущее время, как создать конкретную дату и как форматировать и парсить строки. Всё на примерах из стандартной библиотеки, без сторонних зависимостей.
Текущее время и его компоненты
Всё начинается с
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/PMMST — сокращение таймзоны-07:00 — смещениеZ07:00 — Z для UTC, смещение для остальных📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🤔2❤1😢1
21 мая(в четверг!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Go-разработчика.
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Go-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_go_bot Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1😁1🥱1