Значения без указателей в локальных переменных обычно размещаются на
Это возможно потому, что компилятор заранее знает время жизни переменной: она живёт ровно столько, сколько выполняется функция. Никакой внешней системы управления памятью для этого не нужно.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👾1
Компилятор способен заранее определить, когда стековая память может быть
В случае кучи ни компилятор, ни среда выполнения не могут точно
Please open Telegram to view this post
VIEW IN TELEGRAM
OnceValue принимает
Результат
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Обёртка это замыкание, которое держит ссылки на once, result и f. Пока есть хотя бы одна ссылка на обёртку,
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует
Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.
Как адаптировать продукт и не исчезнуть из выдачи:
— интегрировать
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.
Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.
Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.
Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.
Как адаптировать продукт и не исчезнуть из выдачи:
— интегрировать
MCP и A2A-взаимодействие, чтобы агенты могли вас читать;— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.
Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.
Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.
Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
😁3❤1🌚1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1
Если несколько горутин одновременно требуют одни и те же данные,
singleflight гарантирует, что операция будет выполнена лишьПакет предоставляет единственную структуру
Group, и основной метод Do(key, fn). Если вызов с таким key уже выполняется, новый вызов блокируется и ждёт. Когда первый завершается, все получают одинаковые value, err и флаг shared, сигнализирующий, что результат был разделён.var g singleflight.Group
func fetchData(key string) (any, error) {
v, err, _ := g.Do(key, func() (any, error) {
return loadFromDB(key) // выполнится только один раз
})
return v, err
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤1
Интерфейс с одним методом
Seek(offset int64, whence int) (int64, error). Устанавливает позицию курсора для следующего
Read или Write. Возвращает новое абсолютное смещение от начала файла и ошибку если что-то пошло не так.Ключевое слово «для следующего».
Seek сам ничего не читает и не пишет, он только перемещает внутренний указатель. Следующий вызов Read начнёт именно с этой позиции.Стандартные типы реализующие Seeker:
os.File — файлы на дискеstrings.Reader — строки в памятиbytes.Reader — байтовые срезы в памятиio.SectionReader — ограниченный участок другого ReaderAtPlease open Telegram to view this post
VIEW IN TELEGRAM
С точки зрения читаемости, предпочтительнее
s != "" — сразу очевидно, что s является строкой. len(s) == 0 более универсальный подход, подходящий для слайсов, мап и других типов, поэтому может быть менее выразительным в контексте строк.С точки зрения производительности разницы практически нет. Строка в Go — это структура с указателем и длиной, и пустая строка "" не создаётся заново при каждом сравнении. Компилятор с высокой вероятностью оптимизирует оба варианта к одной и той же проверке длины.
Please open Telegram to view this post
VIEW IN TELEGRAM
👾1
Пишете на Go? Приходите на ВК ДжейТи Митап!
Реальные кейсы и решения из производства, общение с коллегами — всё это в офисах VK:
📍 в Москве — 4 апреля
📍 в Санкт-Петербурге — 11 апреля
В программе два доклада от инженеров VK и неформальная часть:
Москва
• как сократить число запросов с 400 млн до 200 тысяч и построить безопасную платформу мини-приложений
• после — трек на выбор: архитектурная задача с разбором от экспертов VK или обсуждение инженерных новостей в записи подкаста с Никитой Галушко, ведущим разработчиком API ВКонтакте, членом программного комитета Golang Conf/Голанг Конф
Санкт-Петербург
• как построить безопасную платформу мини-приложений и создать высокопроизводительный клиент для Tarantool на Go
• решение архитектурной задачи с обратной связью от инженеров VK
• неформальное общение с коллегами.
Только офлайн. Успевайте зарегистрироваться по ссылке.
Реальные кейсы и решения из производства, общение с коллегами — всё это в офисах VK:
📍 в Москве — 4 апреля
📍 в Санкт-Петербурге — 11 апреля
В программе два доклада от инженеров VK и неформальная часть:
Москва
• как сократить число запросов с 400 млн до 200 тысяч и построить безопасную платформу мини-приложений
• после — трек на выбор: архитектурная задача с разбором от экспертов VK или обсуждение инженерных новостей в записи подкаста с Никитой Галушко, ведущим разработчиком API ВКонтакте, членом программного комитета Golang Conf/Голанг Конф
Санкт-Петербург
• как построить безопасную платформу мини-приложений и создать высокопроизводительный клиент для Tarantool на Go
• решение архитектурной задачи с обратной связью от инженеров VK
• неформальное общение с коллегами.
Только офлайн. Успевайте зарегистрироваться по ссылке.
Канонизация строк это приведение строки к
Одна и та же строка может быть представлена по-разному. Классический пример это Unicode. Символ
é можно записать:- как один кодпоинт
U+00E9- как два кодпоинта
e + U+0301Байтово это разные строки. Визуально — одинаковые. Без канонизации
== вернёт false.В стандартной библиотеке есть пакет
golang.org/x/text/unicode/norm, который реализует четыре формы нормализации по стандарту Unicode:import "golang.org/x/text/unicode/norm"
a := "é" // precomposed
b := "e\u0301" // decomposed
// Без канонизации
fmt.Println(a == b) // false
// С канонизацией
normA := norm.NFC.String(a)
normB := norm.NFC.String(b)
fmt.Println(normA == normB) // true
Где это важно на практике
- Сравнение имён пользователей и email
- Работа с файловой системой (macOS использует NFD, Linux — NFC)
- Поиск по тексту
- Безопасность: канонизация помогает избежать обходов валидации через визуально идентичные, но байтово разные строки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8😁1
Интерфейс:
type WriterTo interface {
WriteTo(w io.Writer) (n int64, err error)
}Суть: тип сам берёт
Writer.Зачем это нужно
io.Copy по умолчанию работает через буфер 32KB и читает кусок из источника, пишет в назначение, и так по кругу. Данные гоняются через userspace.
Но некоторые типы знают более эффективный путь. Например,
*os.File на Linux может использовать системный вызов sendfile и данные перемещаются на уровне ядра, без копирования в память процесса.Как io.Copy это использует
Перед тем как запустить свой буферный цикл, она проверяет интерфейсы:
// 1. Источник умеет писать сам?
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 2. Назначение умеет читать само?
if rf, ok := dst.(ReaderFrom); ok {
return rf.ReadFrom(src)
}
// 3. Fallback — буфер 32KB
Если источник реализует
WriterTo, то io.Copy просто уступает ему управление.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤1
OnceValue принимает функцию Контекст нужно захватить в
// Плохо: ctx может быть уже отменён
ctx := context.Background()
get := sync.OnceValue(func() *DB {
return connect(ctx) // ctx захвачен в замыкании
})
Please open Telegram to view this post
VIEW IN TELEGRAM
👾1
Когда горутин много, останавливать каждую вручную не получится. Нужен способ послать сигнал сразу всем:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for i := 0; i < 5; i++ {
go func(id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("горутина %d остановлена\n", id)
return
default:
fmt.Printf("горутина %d работает\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}(i)
}
time.Sleep(2 * time.Second)
cancel()
time.Sleep(100 * time.Millisecond)
}context.WithCancel возвращает контекст и функцию cancel. Контекст передаётся во все горутины. Каждая горутина слушает ctx.Done() через select. Когда вызывается cancel(), канал ctx.Done() закрывается, и все горутины, которые его слушают, получают сигнал одновременно. defer cancel() в начале гарантирует, что ресурсы освободятся даже если функция завершится раньше времени.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤2
В Go компилятор
Go намеренно не даёт собрать такой код по двум причинам. Первая: порядок инициализации. Если A зависит от B, а B от A, непонятно, что инициализировать первым. Вторая это структура кода. Цикл между пакетами почти всегда сигнализирует о проблеме в архитектуре.
Как решить проблему
Обычно помогает одно из трёх. Вынести общий код в
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
nil-срез это валидное значение с len ==
append(), Go var a []int
a = append(a, 4, 5, 6) // Go выделяет память сам
fmt.Println(a) // [4 5 6]
nil-мапа не
make.var m map[int]int
// m[1] = 1 ← паника!
m = make(map[int]int)
m[1] = 1 // теперь работает
append это функция, которая сама управляет памятью и возвращает новый срез. Мапа же это указатель на структуру данных, и без инициализации этой структуры просто не существует.Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Внутри
WaitGroup хранит Add(n) Done() defer wg.Done() в самом начале горутины.Wait() Done() — основной поток продолжает работу.Please open Telegram to view this post
VIEW IN TELEGRAM
🤔5
Функция возвращает значение, изменения копии не затронут оригинал:
func returnCopy() MyStruct {
return MyStruct{Value: 1}
}Позволяет избежать копирования и работать напрямую с объектом в памяти:
func returnPointer() *MyStruct {
return &MyStruct{Value: 2}
}Функция принимает указатель и напрямую изменяет оригинальный объект без возврата:
func modifyStruct(s *MyStruct) {
s.Value = 3
}Возвращает только нужное поле, а не всю структуру целиком:
func returnValue(s MyStruct) int {
return s.Value
}Позволяет скрыть конкретную реализацию за абстракцией:
type MyInterface interface { DoSomething() }
func returnInterface() MyInterface {
return MyStruct{}
}Возврат набора структур через срез или ассоциативный массив:
func returnSlice() []MyStruct {
return []MyStruct{{Value: 4}, {Value: 5}}
}
func returnMap() map[string]MyStruct {
return map[string]MyStruct{
"first": {Value: 6},
"second": {Value: 7},
}
}7. Возврат через канал
Передача структуры между горутинами для конкурентных сценариев:
func sendToChannel(ch chan MyStruct) {
ch <- MyStruct{Value: 8}
}Эти подходы можно комбинировать и адаптировать под разные сценарии — учитывая требования к производительности и управлению памятью.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔15❤2🌚2👍1
Circuit Breaker это защитный паттерн для распределённых систем. Он автоматически «отключает» вызовы к
Аналогия проста: как автоматический выключатель в электрощитке защищает проводку от перегрузки, так и Circuit Breaker защищает систему от цепной реакции ошибок.
Два состояния
Замкнуто — штатный режим. Все запросы
Разомкнуто — защитный режим. После превышения порога ошибок
Реализация:
type Circuit func(context.Context) (string, error)
func Breaker(circuit Circuit, failureThreshold uint) Circuit {
var consecutiveFailures int = 0
var lastAttempt = time.Now()
var m sync.RWMutex
return func(ctx context.Context) (string, error) {
m.RLock()
d := consecutiveFailures - int(failureThreshold)
if d >= 0 {
// Экспоненциальная выдержка: 2, 4, 8... секунд
shouldRetryAt := lastAttempt.Add(time.Second * 2 << d)
if !time.Now().After(shouldRetryAt) {
m.RUnlock()
return "", errors.New("service unreachable")
}
}
m.RUnlock()
response, err := circuit(ctx)
m.Lock()
defer m.Unlock()
lastAttempt = time.Now()
if err != nil {
consecutiveFailures++
return response, err
}
consecutiveFailures = 0 // Успех — сбрасываем счётчик
return response, nil
}
}
Что важно в этой реализации
•
sync.RWMutex защищает общее состояние при конкурентных вызовах• Экспоненциальная выдержка (
2 << d) даёт сервису всё больше времени на восстановление с каждой неудачной попыткой• После успешного вызова счётчик сбрасывается — цепь «замыкается» обратно автоматически
• Функция возвращает тот же тип
Circuit, что позволяет прозрачно встраивать Breaker без изменения кода клиентаPlease open Telegram to view this post
VIEW IN TELEGRAM
👍8😁1
Package aliasing это возможность присвоить псевдоним
Синтаксис:
import fm "fmt"
fm.Println("hello") // вместо fmt.Println
Алиас указывается перед путём к пакету и полностью заменяет его имя в текущем файле.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
📌 Кейсы использования
Два пакета с одинаковым именем. Без алиасов код не скомпилируется.
import (
sqlDB "project/sql/db"
nosqlDB "project/nosql/db"
)
Сокращает шум при частом обращении к пакету с громоздким путём:
import (
mh "myproject/subproject/module/helpers"
)
Актуально для сгенерированного кода, когда имя пакета не совпадает с тем, что ожидает читатель:
import (
validator "github.com/myorg/gen/v2/validate_pb"
)
_Специальный алиас для импорта ради побочных эффектов: регистрация драйвера,
init()-функция; без использования пакета в коде:import _ "github.com/lib/pq" // регистрирует PostgreSQL-драйвер
.Позволяет обращаться к экспортируемым именам пакета без префикса:
import . "math"
r := Sqrt(16) // вместо math.Sqrt(16)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9