Go Update
3.23K subscribers
12 photos
85 links
Канал про новости связанные с языком программирования Go. Эволюция языка, стандартной библиотеки и просто интересные вещи над которыми работает Go Core Team и не только.

Админ: @lepage_d
Download Telegram
Запись нашего февральского с Эдгаром подкаста о том как мы дошли до жизни такой.
3
А ещё внезапно узнал, что у меня новая фамилия.
😁22🗿6
✍️ go fix: apply fixes from modernizers ✍️

Есть такая утилита в тулчейне Go — go fix. В задачу которой входила модернизация кода под новые конструкции — как в языке, так и в стандартной библиотеке. Эдакий аналог go fmt который вместо единого стиля, выравнивал использования языковых конструкций по кодобазе. И вроде отличная идея, но с одной проблемой: почти десять лет никто её не поддерживал и не модернизировал. Те сама утилита осталась в далеких временах Go 1.8. Причина подобного довольно тривиальна: нет необходимости в обобщенной модернизации кода, а значит нет и свободных ресурсов под это. Тем более сам go fix был написан с использованием большого числа костылей, которые усложняли любую попытку модернизации.

Однако всё изменилось с приходом LLM: модели OpenAI, Anthropic, Alibaba и прочих обучаются на огромных массивах данных в свободном доступе и код генерируют исходя из увиденного. И тут стало понятно: если модели всегда будут учится на старом коде, то и предлагать они будут всегда старые паттерны. Поэтому, недавно вернувшийся в Google, Алан Донован (да-да, тот самый от кого у вас та самая голубая книжка) всерьез озаботился вопросом модернизации кода.

Первый шаг — пускаем старый кода под нож. Полностью.
Второй шаг — это полная переделка команды на основе различных анализаторов из gopls. Которые, в свою очередь, используют фреймворк analysis.

Результат: у нас есть «модернизатор» кода который заменяет «устаревшие» паттерны их современными аналогами.

Приведу несколько примеров. Начиная с 1.26 подъехал новый синтаксис new который умеет в значения. А модернизатор умеет распознавать где он нужен:


data, err := json.Marshal(&RequestJSON{
URL: url,
Attempts: newInt(10),
})

func newInt(x int) *int { return &x }


Становится


data, err := json.Marshal(&RequestJSON{
URL: url,
Attempts: new(10),
})


Тоже самое касаемо использования конкатенации строк внутри циклов:


s := ""
for _, b := range bytes {
s += fmt.Sprintf("%02x", b)
}
use(s)


Становится


var s strings.Builder
for _, b := range bytes {
s.WriteString(fmt.Sprintf("%02x", b))
}
use(s.String())


Те из коробки мы получаем и более краткий и более производительный код. Еще одна фича, которая доступна ещё и «обычным смертным»: новая аннотация //go:fix inline которая позволяет проводить inlining функций. Те если go fix встроен в ваш пайплайн, все перестановки произойдут автоматически, что удобно когда у вас был большой рефакторинг и внешних пользователей надо уведомить о новом расположении (или сигнатуре) функций.

Сама команда Go Core опубликовала отличные статьи которые показывают паттерны использования нового функционала и внутрянку того, как это работает. Рекомендую к прочтению.
👍9🔥74
🩳 proposal: spec: type inferred composite literals 🩳

Вы когда-нибудь задумывались, почему можно писать вот такой код:


type T struct{ V int }

a := []*T{{0}, {1}, {2}}
b := map[string]*T{"a": {0}, "b": {1}, "c": {2}}
c := [3]T{{0}, {1}, {2}}


Но нельзя вот такой:


var x []string = {"a", "b", "c"}
var y []*T = {{0}, {1}, {2}}
var z T = {0}


Всё дело в довольно сложных правилах вывода типов для сложных типов. Ещё в первых версиях создатели языка резонно решили, что одного объявления при инициализации достаточно и нет смысла дублировать идентификатор типа для каждого элемента отдельно. Что, в свою очередь, очень помогло в табличных тестах. Но вот сделать ещё один шаг им не хватило смелости уверенности в том, что код сохранит свою читаемость. А это было и остаётся критически важно для языка.

Однако всё течёт, всё меняется, и запрос на tuples (кортежи) или их аналог регулярно всплывал в issue-трекере языка. Причину для этого запроса лучше всего описать следующим кодом:


ch := make(chan struct {
value string
err error
})

//...

ch <- struct {
value string
err error
}{value: "result"}


Расписывать описание анонимной структуры и в момент декларации, и в момент использования очень неудобно, что нередко приводило к ситуациям, когда люди создавали локальный тип (или использовали алиас), чтобы не писать описание повторно.

И вот теперь, наконец мы стали TypeScript появится (скорее всего) обратный вывод типов для всех вариантов присваивания. Т. е. станет корректным следующий код:


ch <- {value: "result"}

fnT({0})

var s []*T = {{0}, {1}, {2}}

return {}, err


Последнее меня особенно радует, так как во многих кодобазах люди специально использовали возврат указателей из функции, так как не хотели писать длинное имя типа с пакетом.

А из дополнительных плюсов, данное изменение приведёт к упрощению (!!!) спеки языка, а значит разработчикам компиляторов Go станет чуть легче жить.

Текущий статус: likely accept accepted! и потенциальный кандидат на часть релиза 1.28.
👍24🔥3
⚠️ CVE-2026-42501 или почему вам нужно обновиться до Go 1.26.3/go1.25.10 ⚠️

Те кто давно работают с Go (или читают меня) знают, что целостность наших модулей гарантируется не только HTTPS транспортом до самой прокси, но и отдельной базой SumDB, которая хранит в себе хеши модулей подписанные приватным ключом. При этом сама база устроена так, что невозможно внести изменения в прошлые записи, без каскада изменений в новые, тк каждый блок «подписан» предыдущим.

Но в ИБ сама безопастность это устойчивость самого слабого звена в цепочке. Достаточно одной маленькой ошибки, что-бы вся надежно выстроенная архитектура ИБ превратилась в пыль. Так и случилось в нашем случае.

Рассмотрим сценарий: при получении модуля от прокси, Go проверяет его хеш с использованием SumDB, блоки которой, как я уже говорил выше, подписаны приватным ключом. В псевдокоде это выглядит следующим образом:


for _, line := range sumdbResponse {
if line относится к нужному module@version {
if hash не совпадает {
return SECURITY ERROR
}
}
}
return nil


На первый взгляд, всё кажется логичным: если пришел ответ, проверяем хеш нужного модуля и при несовпадении валимся с ошибкой. При совпадении выходим из функции и продолжаем работу. Однако здесь кроется одна маленькая, но критичная деталь: что будет если нам вернут ответ без записей или запись про другой модуль? Скомпроментированная прокся может отдать специально подготовленный ответ, который наш клиент воспримет как правильный — достаточно выдать ответ с корректными хешами для любых модулей или даже выдать ответ без хешей. Поскольку go get пишет записи в go.sum с локально подсчитанного хеша, подмену никто не заметит, и тулинг спокойно продолжит работу, положив модуль от скомпрометированной прокси в go.mod/go.sum. Который, в свою очередь, будет использован компилятором при сборке проекта.

Т.е. перед нами классическая уязвимость которая позволяет организовать Supply Chain Attack (атака на зависимости), пример которой можно описать вот так:

1. Прокся отдаёт клиенту изменённый архив модуля, например rsc.io/foo@v1.0.0.
2. На запрос хеша возвращается валидный, криптографически корректный ответ от checksum database, но для другого модуля, например rsc.io/bar@v1.0.0.
3. go get видит: «ответ sumdb валидный, несовпадающего хэша нет» — и продолжает работу.

Т.е. вы получаете измененный модуль, у которого хеш оригинала можно узнать только после удаления go.sum и изменения прокси или использования direct в GOPROXY.

Проблема бы не была такой большой (ибо у большинства переменную GOSUMDB, отвечающую за источник правды о хешах, никто не трогает, а для загрузки тулчейна её вообще невозможно переопределить) если бы не два но:

• Директивы toolchain и go позволяют выбирать нужную версию компилятора автоматически, и при необходимости, скачивать её с той же самой прокси.
go get всегда сначала спрашивает проксю, может ли та проксировать запросы до базы хешей GOSUMDB. Если ответ положительный, то она просто спросит её о хеше, вместо обращения непосредственно к GOSUMDB.

В этой ситуации фактически всю цепочку доставки модулей до клиента (и тулчейнов, если явно не запрещен автовыбор) контролирует один узел. Поэтому, если у вас изменена GOPROXY или вы не доверяете сертификатам TLS установленным в системе, то я очень рекомендую вам обновиться. При этом вам недостаточно обновить версию go (или toolchain) в файле go.mod, ведь изначальный тулчейн будет по-прежнему работать по старой логике. Нужно именно установить новую версию компилятора, заменив ту которая стоит «по умолчанию» в системе, а затем прогнать rm go.sum && go mod tidy в проектах которые могут быть скомпрометированы.

При всей опасности атаки, фикс у неё самый тривиальный: return nil просто заменили на


return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum missing from sumdb response"+sumdbAbsent, noun))


П.С. Именно поэтому я рекомендую использовать приложения типа direnv для установки переменных GOPROXY или GOPRIVATE только для рабочих проектов.
👍10🔥2
Я редко пишу сюда о вещах которые не относятся к Go, но тут у меня появилась хорошая статья на вечер. Которая про «архитектуру безопастности» и «самом слабом звене» и перекликается с прошлым сообщением. Только в обычном-бытовом значении.

Рекомендую.

ПС. Все совпадения реальны 😊️️️️️️.
🗿7🤮43😁1
📝 net/http/httptest: synctest support

Я уже писал про пакет synctest и его возможности. Это была одна из главных фишек Go 1.25, здорово облегчившая тестирование конкурентного кода. Главный минус данного пакета, на мой взгляд, это его слабая интегрированность в стандартную библиотеку. И, похоже, эту проблему наконец решено начать исправлять.

Большинство из нас работает с HTTP-серверами (включая gRPC, который является сабсетом HTTP/2) в том или ином виде. Для тестирования этих серверов у нас есть пакет net/http/httptest, который позволяет легко поднять тестовый HTTP-сервер, отвечающий на запросы, используя реальный loopback-интерфейс. Запущенный тестовый сервер также легко создаёт подключенного к себе клиента, который, в свою очередь, работает одинаково хорошо с обычным соединением и TLS-режимом без сложных махинаций с корнем сертификатов. Однако у этого подхода есть минус: так как он использует реальный сетевой интерфейс и порт (и, как следствие, сокет), пакет synctest с ним несовместим.

Проблемой озаботился Дэмиен Нил (автор самого synctest) и предложил добавить в пакет httptest новую функцию: NewTestServer. Суть заключается в том, что если на полученном сервере не вызывать метод Start, то все запросы пойдут через in-memory-шину, реализованную на каналах. Таким образом уходит IO, и процесс становится контролируемым через synctest.

При этом все запросы клиента, полученного от созданного сервера, вне зависимости от целевого адреса, будут уходить на сервер, породивший клиента (в отличие от клиентов для обычных тестовых серверов, где запросы могут уходить и на сторонние адреса). Если же на полученном сервере вызвать метод Start, он будет вести себя так же, как и раньше вёл себя тестовый сервер - поднимет сокет слушающий на loopback-интерфейсе.

Вместе с synctest такой подход позволяет создавать серверные юнит-тесты, где можно отслеживать статус сервера, хендлеров, прочих сущностей в любой момент времени без лишних костылей. И ловить утечку горутин после завершения тестов.

Текущий статус: accepted, но я не уверен, что фича попадет в 1.27 из-за скорого фича-фриза.
🔥175👍42
📝 testing: allow examples with any signature

Небольшое «Quality of Life» предложение. Суть: давайте разрешим функциям Example*, которые мы используем для документации, принимать и возвращать любое число аргументов. Запрос связан с тем, что сейчас многие вещи не выразить в примерах. Например примеры для пакетов нацеленных на тестирование.

Те в документации станет отображаться следующий код:


func Example_Functionname(t *testing.T) {
t.Logf("foobar")
}


Единственным ограничением является то, что в отличии от безаргументных Example* функций, они не смогут содержать // Output: комментарии.


func Example_Functionname(t *testing.T) { // will not compile
t.Logf("foobar")
// Output: foobar
}



Напомню, что такие комментарии автоматически переводят примеры в тесты — их прогоняет go test и сверяет их вывод с тем, что идёт в блоке комментария после Output. Что было довольно удобно для проверки (и сохранения!) корректности этих самых примеров. Решать сие предлагают через написание тестов, которые будут вызывать эти примеры напрямую — т.е. точно так-же как мы тестируем обычные функции.

Текущий статус accepted, и будет сие вероятно частью релиза 1.28.
😐8👍7👎1
Go Update
🎂 Настал тот день когда мне исполнилось 33! Да, да тот самый год, в который вас поздравляют используя аналогии к одной из самых ярких личностей в истории человечества 😁️️️️️️. В (не)далеком детстве, когда мне было лет 7-8, я смотрел на взрослых, которым…
🎂 Вечерний пост о том, что сегодня мне исполнилось 34.

Прошёл еще один год, а значит время «подводить итоги». Однако вот сижу я сейчас и думаю: это сообщение, вероятно, смог бы написать за меня OpenAI Codex (или Claude Code моей жены). И неподготовленный взгляд, опять же, вероятно, даже не заметит подмену. Однако сии строки я набираю сам, со всеми ошибками и опечатками. И вот почему.

За год LLM окончательно завершили технологическую «революцию», закрепившись в мире разработки, перестав быть экзотикой и стал очередным жестким требованием работодателей. Бессмысленно отрицать, что большие языковые модели заменили нам исследование, прототипы, поиск в Гугле и на SO. Настройку и развертку. Исследование логов. Написание ADR. У многих даже ручной кодинг отвалился. Да че уж говорить, моя команда по выводу продуктов в Open Source из маркетинговой истории «смотрите как у нас круто» превратилась в прагматичную историю «можете натравить на наш API/SDK ваших агентов и они соберут вам всё за пару вечеров». Скорость и объем итераций возросли в разы.

Однако у всего этого есть и обратная сторона. Я не буду говорить про выгорание от попыток вразумить «электронного дебила» через набор текстовых файлов. Я не буду говорить про кратно возросшую нагрузку из-за потока MR на code-review. Я не буду говорить, что новички в профессии уже не стесняются говорить о том, что «ну за меня код GLM/DeepSeek пишет, сам я тока рулетку кручу промт вставляю». И уж тем более я не буду говорить о том, насколько выросли цены на железо и что любой корпоративный сервис, вне зависимости от сложности, теперь является потенциальной жертвой 12летнего куллхацкера с локальным Heretic Qwen и неограниченным запасом времени. Про всё это уже написано огромное число статей, а реальные выводы мы сможем сделать лишь спустя годы.

Нет, моя мысль более приземленная. За три года, которые я взаимодействую с языковыми моделями, я пришёл к однозначному выводу — LLM это невероятно крутой Т9. Я понимаю, что многие из вас, кто работают с моделями (или работает над ними), сейчас оскорбились, но моя цель не принизить успех технологии. Отнюдь — как я уже говорил, реальный импакт уже есть и он уже ощущается всеми. Но и замечать очевидные минусы становится всё сложнее.

Минус первый — пустословность моделей. Именно пустословность, а не многословность, тк все мы знаем про Caveman Skill и прочие различные методики сокращения числа потребляемых токенов. Нет, речь именно про смысловую нагрузку в тексте, который генерирует модель, неком числе коэффициенте полезной информации на слово. Из-за того, что каждый второй сейчас выкладывает тексты «причесанные LLM» я обратил внимание, что не могу больше читать и осознавать такие тексты. Из-за их огромного числа, мозг перестал их воспринимать. Как рекламу — она вроде есть, но мозг научился её фильтровать настолько хорошо, что мы даже не обращаем внимание на баннеры слева и справа потребляемого контента. И из минуса следует логичный вопрос: для кого написаны эти километры «причесанного LLM» текста? Кто их будет читать?

Минус второй — мы думали (думаем?) что это заменитель, а это мультипликатор, знаний и ума. В индустрии всё еще теплится идея, что модели смогут заменить «мешков с костями» как полноценные автономные сотрудники. Не буду касаться вопроса ответственности, зайду с другой стороны: на днях общаясь с Claude Opus 5 я обратил внимание, что имена и факты не сходятся. При этом текст был лаконичный и набор утверждений были последовательным. Нигде модель не путалась, не мешала следствия и не выглядела как шизофреник. Однако одно неправильное утверждение вызвало у меня вопрос «а откуда ты взяла XY?». На что модель честно ответила — я это придумала! Как и 50% всех фактов, которые она мне до этого любезно разложила. И хуже здесь не сама галлюцинация (которые нам всё обещают победить, но никак не побеждают), а тот факт, что модель смогла вывести идеально выстроенную систему из неё. Если бы я не знал о конкретном несоответствии, я бы даже не подумал, что что-то пошло не так. Настолько был убедительный текст.
27👍15
Go Update
🎂 Настал тот день когда мне исполнилось 33! Да, да тот самый год, в который вас поздравляют используя аналогии к одной из самых ярких личностей в истории человечества 😁️️️️️️. В (не)далеком детстве, когда мне было лет 7-8, я смотрел на взрослых, которым…
И вот эти два минуса выглядят нерешенными (на данный момент времени). Можно ли их решить вообще, я тоже не знаю — как я уже сказал выше, галлюцинации нам обещали победить еще в середине 25го. Но именно сочетание двух проблем вызывает у меня тревогу, особенно с учётом мировых ожиданий от LLM. Мне кажется, что мы находимся в очередной фазе «когда у меня в руках молоток, то всё перед мной — гвозди». Только одна сложность — у молотка «нет тормозов». Совсем. Достаточно упорный запрос заставит сий «молоток» выполнить любую функцию, или (что хуже) доложить вам о её успешном выполнении. Как говорил один интернет классик — «любой ценой, но бесплатно!».

Я очень надеюсь, что эта фаза временная, но уже сейчас вижу какой мешок проблем нам придется разгребать когда ажиотаж спадёт если нас всех к тому моменту не «оптимизируют». Ситуация безусловно не станет прежней, но и окончательные выводы о всеприменимости технологии пока ещё делать рано. Умнейшие умы человечества изобрели «двигатель». Осталось изобрести «тормоза и рулевое колесо». И ремни безопасности на сдачу.

П.С. Да это очередное брюзжание про LLM о котором вы не просили 😁. Интересное постараюсь опубликовать в течении недели. Рабочее название «Теперь я знаю, почему никто не спрашивает на собесах о том, как работают type assertion».
28🔥7👍3🤯1
Об изоляции LLM

Да, это ещё один пост про работу с Codex/Claude/GLM/Qwen/DeepSeek и прочая. В этот раз без воды, исключительно технический.

Решил я тут вкатится в разработку с помощью LLM всерьез и пойти через, уже почти ставшие стандартом, «план -> реализация -> тест -> повторить». Касаемо шагов, скиллов, плагинов и прочего говорить не буду — тут каждый сам «кузнец своего счастья» и индустрия пока только осознает необходимость общего подхода. Меня же заинтересовало другое — изоляция агентов, в процессе их работы, от деструктивных и «вредных» действий.

Проблема: «настоящая» изоляция ни у одного из агентов/харнессов не сделана на приемлемом уровне «из коробки». OpenCode запрещает чтение выше своего рабочего каталога, но спокойно пропускает команду head -10 /home/file/name. Codex и Claude, используя API песочницы ОС, спокойно пропускают ручки LLM до хомяка и других каталогов, правда только на чтение. Однако даже чтение может быть «вредным»: если агент в своём безумии решит вычитать ~/.ssh/private_key, то «по умолчанию» ему никто не помешает это сделать. И да, я знаю, что у SOTA моделей проверкой команд занимается ещё одна модель в отдельном контексте, но это вероятностная проверка, а не однозначная. А значит, если она оставляет 1% шанс на чтение моего приватного ключа, то это не вопрос «может быть?», а вопрос «когда?».

Поэтому мои поиски привели меня к следующим тулам и вариантам:
Docker SBX — sandbox для агента поверх докера. На всех ОС (Linux/Windows/Mac) изоляция сделана с помощью виртуализации, а значит если чего-то в образе нет, то к этому «чему-то» агент доступа не получит. Плюсы: знакомый Dockerfile синтаксис и принципы, хорошая интеграция с docker’ом. Минусы: необходимость Docker ID и долгий запуск, жрёт от 500+ метров в памяти. Для разработки мультиконтейнерных решений хорошая вещь, для «пообщаться c OpenCode» не.
• Система правил для Claude и Codex — можно конфигом настроить, что агенту можно, и обе тулы зафиксируют правила через механизмы песочницы ОС. Плюсы: есть в комплекте (хотя стандартные правила слишком свободные), нулевые дополнительные траты на песочницу, (вроде) достаточно гибкий синтаксис. Минусы: у каждой тулы свой синтаксис, у кодекса сама система всё еще в экспериментальном статусе, ограничения песочницы самой ОС. Если работатете строго с одним агентом, то это может быть хорошим вариантом.
Agent Safehouse — унифицированная песочница для агентов на MacOS. Работает через sandbox-exec (через него же работает песочница клода и кодекса). Плюсы: нулевая стоимость на этапе исполнения, унифицированный синтаксис и команды для запуска процессов в песочнице. Минусы: выглядит как сторонний проект, поддержка которого может в любой момент закончится. Все ограничения песочницы мака в силе, а само яблоко регулярно меняет API sandbox-exec, которое ещё и не документирует. Для «пообщаться с OpenCode» мне нравится, но в прод я бы такое тащить не рискнул.
MicroSandbox, SmalVM — идея та-же, что у SBX, но реализация другая - под капотом микровирутальные машины на которые накатываются такие-же OCI миниобразы. Из-за этого запуск кратно быстрее SBX и кратно меньше потребление памяти (обещают оверхед в 30-40Мб против 500+ у докера). Плюсы: у обоих удобный синтаксис, возможность точечной настройки доступов к сети (включая варианты через MITM и резолвинга только определенных доменов) и ресурсам железа, аудит событий внутри виртуалки, адекватная работа с секретами. Минусы: оба проекта вроде выходят на коммерческие рельсы, но пока именно в процессе выхода. Запуск виртуалки, несмотря на минимализм, все же сьедает дополнительное время и память. Для прода я бы рассматривал этот вариант, тем более у smolvm есть возможность преобразовать итоговые образы в исполняшки.

Для себя пока остановился на Safehouse, плюс активно исследую MicroSandbox. Надеюсь, что всё в итоге превратится в набор стандартов, по типу OCI, ибо так дальше жить нельзя.
👍122👏1