Мы недавно писали про
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
👍7❤4🔥4
Разберём базу: как получить текущее время, как создать конкретную дату и как форматировать и парсить строки. Всё на примерах из стандартной библиотеки, без сторонних зависимостей.
Текущее время и его компоненты
Всё начинается с
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
👍9🤔2❤1😢1
21 мая(в четверг!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Go-разработчика.
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Go-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_go_bot Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2😁1🥱1
🧑💻 Вы прошли техсобес. Задачи решены. Оффер получил другой
Это vibe hiring — найм по ощущению, где решение принимается до того, как рекрутер посмотрел на ваши компетенции.
В статье — данные двух крупных исследований 2025–2026 годов, гендерная статистика по фидбеку с интервью и объяснение, почему субъективный найм бьёт по самой компании сильнее, чем по кандидату.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
Это vibe hiring — найм по ощущению, где решение принимается до того, как рекрутер посмотрел на ваши компетенции.
В статье — данные двух крупных исследований 2025–2026 годов, гендерная статистика по фидбеку с интервью и объяснение, почему субъективный найм бьёт по самой компании сильнее, чем по кандидату.
📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3😢2🥱2
Разберём тип
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"
📍 Навигация: Вакансии • Задачи • Собесы
#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 мая — очень ждём именно тебя!
Команда GigaChat зовёт на One Day Offer амбициозных Java-разработчиков, которые готовы создавать AI‑продукты уровня BigTech и стать частью крупнейшего AI-комьюнити.
Если ты дружишь с Java (версии 8–25), ладишь со Spring и Hibernate, а PostgreSQL и ClickHouse для тебя — не просто слова, переходи по ссылке и занимай слот на One Day Offer.
Встречаемся 23 мая — очень ждём именно тебя!
🥱3😁1
Senior Golang Developer — офис в Сербии
Senior Golang-разработчик — от 300 000 ₽, удаленно по Москве
Golang-разработчик — до 150 000 ₽, удаленно или гибрид в Москве
#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM