Иногда нужно дать пользователям или операторам возможность менять поведение программы без пересборки — конфиги не всегда справляются, а встраивать Lua кажется чужеродным.
let-go предлагает другой вариант: язык в духе Clojure, написанный на Go и встраиваемый прямо в приложение.
Что это такое
let-go — это компилятор байткода и виртуальная машина для языка, который вплотную копирует Clojure. Реализован на Go, распространяется как обычный модуль.
Это не замена оригинального Clojure и не инструмент для его форматирования, а именно встраиваемый язык расширения.
Статус проекта — WIP, но уже работающий: автор решал на нём задачи Advent of Code 2022.
Что уже есть
Из реализованного: макросы с syntax quote, деструктуризация, функции с несколькими арностями, атомы, каналы и go-блоки в стиле
core.async, регулярные выражения, простые пространства имён json, http и os, REPL с подсветкой синтаксиса и автодополнением, базовый nREPL-сервер совместимый с Calva.Установка и запуск
go install github.com/nooga/let-go@latest
Запустить REPL:
let-go
Выполнить выражение напрямую:
let-go -e '(+ 1 1)'
Запустить файл:
let-go test/hello.lg
Запустить файл, а потом открыть REPL:
let-go -r test/simple.lg
Как выглядит код
Синтаксис — чистый Clojure. Пример с каналами и go-блоками:
(def ch (chan 10))
(go (>! ch "hello from goroutine"))
(println (<! ch))
Деструктуризация и работа со списками:
(let [[a b & rest] [1 2 3 4 5]]
(println a b rest))
; => 1 2 (3 4 5)
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁2
🔥 Знакомьтесь с экспертом Proglib.academy: Эмиль Сатаев
Эмиль — эксперт с 8-летним опытом в разработке, который специализируется на внедрении LLM и агентных подходов в реальные коммерческие сервисы. Он точно знает, как проектировать архитектуру так, чтобы ИИ-функции работали стабильно в связке с внешними системами.
🏃♀️ Уже 14 мая Эмиль проведет открытый вебинар!
Обсудим самую «больную» тему: «Почему AI-продукты на базе LLM ломаются и как сделать, чтобы работало».
🗓 Когда: 14 мая в 19:00 (Мск)
Почему Эмиля стоит послушать:
🟣 8+ лет в разработке (Backend и Frontend)
🟣 Международный исследовательский опыт
🟣 Преподаватель-практик
🟣 Мастер интеграции AI в Backend
🔗 Зарегистрироваться на вебинар
Эмиль — эксперт с 8-летним опытом в разработке, который специализируется на внедрении LLM и агентных подходов в реальные коммерческие сервисы. Он точно знает, как проектировать архитектуру так, чтобы ИИ-функции работали стабильно в связке с внешними системами.
Обсудим самую «больную» тему: «Почему AI-продукты на базе LLM ломаются и как сделать, чтобы работало».
🗓 Когда: 14 мая в 19:00 (Мск)
Почему Эмиля стоит послушать:
Прошел путь от фулстека до Backend Platform Developer в SMIT.Studio.
Работал исследователем в Институте ИИ НИУ ВШЭ и в Национальном университете Сингапура (NUS).
Ведет семинары в НИУ ВШЭ, в том числе по проектированию и разработке агентских систем.
Его главная суперсила — умение правильно встраивать LLM через API, выстраивать workflow и агентную логику в сложных распределенных системах.
🔗 Зарегистрироваться на вебинар
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱6👍3❤1
Go долго жил без дженериков. Если нужно было написать одну и ту же функцию для
int и float64, приходилось копировать код или использовать interface{} с приведением типов. В версии 1.18 это наконец исправили.Разберём, что такое дженерики в Go, как они работают и зачем нужны type constraints.
Зачем нужны дженерики
Представьте, что вам нужна функция поиска минимума. Без дженериков пришлось бы писать отдельную версию для каждого типа:
func MinInt(a, b int) int {
if a < b { return a }
return b
}
func MinFloat(a, b float64) float64 {
if a < b { return a }
return b
}С дженериками всё сводится к одной функции:
func Min[T cmp.Ordered](a, b T) T {
if a < b { return a }
return b
}Теперь
Min работает с любым типом, который поддерживает сравнение. Вызывается так же просто, как обычная функция. Go сам определяет тип по аргументам:
fmt.Println(Min(3, 5)) // 3
fmt.Println(Min(3.14, 2.71)) // 2.71
fmt.Println(Min("a", "b")) // "a"
Синтаксис
Типовой параметр указывается в квадратных скобках перед обычными аргументами.
После имени параметра идёт constraint, которое определяет, какие типы допустимы:
func Print[T any](value T) {
fmt.Println(value)
}Здесь
T any означает, что T может быть чем угодно. Можно использовать несколько типовых параметров:
func Pair[T, U any](first T, second U) (T, U) {
return first, second
}Встроенные ограничения
Go предлагает несколько готовых constraint'ов.
any принимает любой тип. Подходит, когда вам не нужны никакие операции над значением, кроме передачи и хранения.comparable принимает типы, которые можно сравнивать через == и !=. Полезно для поиска по слайсу или использования в качестве ключа map.func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}cmp.Ordered из пакета cmp (Go 1.21+) принимает типы с поддержкой операторов <, >, <=, >=. Это все числовые типы и строки.func Max[T cmp.Ordered](values ...T) T {
max := values[0]
for _, v := range values[1:] {
if v > max {
max = v
}
}
return max
}Свои ограничения
Можно создавать собственные constraint'ы через интерфейсы с перечислением типов:
type Number interface {
int | int8 | int16 | int32 | int64 |
float32 | float64
}
func Sum[T Number](values []T) T {
var sum T
for _, v := range values {
sum += v
}
return sum
}Оператор
~ позволяет принимать не только базовые типы, но и все типы, основанные на них. Если у вас есть
type MyInt int, то constraint ~int примет и MyInt:type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type MyInt int
func Double[T Integer](v T) T {
return v * 2
}
var x MyInt = 5
Double(x) // работает, потому что MyInt основан на intConstraint'ы можно комбинировать. Если нужен тип, который одновременно поддерживает сравнение и имеет метод
String(), это тоже реализуемо:type OrderedStringer interface {
cmp.Ordered
fmt.Stringer
}Дженерики убирают дублирование кода без потери типобезопасности. В Go они реализованы лаконично, без сложной иерархии типов.
📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15❤4⚡1
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
👍10🔥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
Допустим, у вас есть строка байтов:
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
👍4❤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
❤6👍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
⏳ 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
❤9