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

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

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

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

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

#WXSSA
Download 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
👍74🔥4
Пакет 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
👍9🤔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
👍2😁1🥱1
🧑‍💻 Вы прошли техсобес. Задачи решены. Оффер получил другой

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

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

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

🐸 Библиотека Go-разработчика
Please open Telegram to view this post
VIEW IN TELEGRAM
3😢2🥱2
Пакет time в Go: длительность, арифметика и таймзоны

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

Duration

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


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


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


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


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

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


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


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


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


Сравнение

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

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


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


Таймзоны

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


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


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


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


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


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


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

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

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Твой код — в сердце мощного ИИ! 💚

Команда GigaChat зовёт на One Day Offer амбициозных Java-разработчиков, которые готовы создавать AI‑продукты уровня BigTech и стать частью крупнейшего AI-комьюнити.

Если ты дружишь с Java (версии 8–25), ладишь со Spring и Hibernate, а PostgreSQL и ClickHouse для тебя — не просто слова, переходи по ссылке и занимай слот на One Day Offer.

Встречаемся 23 мая — очень ждём именно тебя!
🥱3😁1
🤨 Топ-вакансий для Go-разработчиков за неделю

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

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

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

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

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

#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM