11 year old unsoundness bugs found in Rust borrow checker 🔥 Горячее 💬 Длинная дискуссия
В Rust обнаружена ошибка типа P-high, позволяющая обойти требования времени жизни полей структуры через ковариантное присваивание. Суть в том, что компилятор некорректно проверяет владение ссылками, помещёнными в обёртку, которая затем хранится в статическом контейнере.
В примере структура Q<T> хранит опциональную ссылку и при уничтожении переносит её в глобальный Mutex<Vec<Box<dyn Any + Send>>>. В main ссылка на локальную строку l присваивается полю q.0, область видимости l завершается, но q уничтожается позже — в момент выхода из блока. Поскольку Q объявлен как Send и 'static, компилятор считает ссылку валидной всё время жизни программы. В результате в статическом векторе оказывается ссылка на уже уничтоженную строку, которую можно безопасно привести к &'static String и распечатать.
Ошибка существует уже 11 лет и не имеет статуса P-critical исключительно из-за её давности — за это время она не была найдена в реальной практике. Для эксплуатации требуется намеренно вредоносная реализация Drop, поэтому случайная утечка памяти маловероятна. При включённом AddressSanitizer программа завершается с ошибкой stack-use-after-scope. Исправление входит в приоритетную программу Types Team по устранению некорректности системы типов.
Комментарии (373)
Найденный баг — ошибка компилятора, а не типовой системы. Его исправление требует добавления проверки, но может сломать существующие библиотеки из-за простого репродуктора. В репозитории Rust более ста открытых проблем с несогласованностью; новый решатель трейтов обещает упростить их исправление. AI‐поиск ускоряет обнаружение таких багов, но полная надёжность недостижима из‐за неизбежных компромиссов и постоянного добавления новых функций. Редкое использование вариантных линтов позволяет ошибкам оставаться незамеченными годами, даже при обширном тестировании. Разработчикам рекомендуется проверить зависимости на наличие аналогичных паттернов перед применением фикса. AI‐поддерживаемый поиск в коде компилятора Rust приближает язык к формальной верификации, но не гарантирует отсутствия скрытых багов.
Go Concurrency Distilled 🔥 Горячее 💬 Длинная дискуссия
Go предоставляет лёгкие горутины, запускаемые ключевым словом go, которые планируются рантаймом на системные потоки. Для синхронизации используются sync.WaitGroup, увеличивающий счётчик при Add и уменьшающий при Done, а Wait блокирует до нуля. Каналы обеспечивают синхронный обмен данными: отправка блокирует до получения, а select позволяет ждать нескольких каналов одновременно, включая time.After для таймаутов. Контекст (context.Context) передаёт сигналы отмены и таймауты через WithCancel и WithTimeout, проверяемые через Done(). Для защиты共享 данных применяются мьютексы (sync.Mutex), а атомарные операции (sync/atomic) работают без блокировок. Семафоры ограничивают доступ к ресурсам через буферизованные каналы, а sync.Once гарантирует однократную инициализацию. Профилирование выполняется через net/http/pprof или вручную: pprof.StartCPUProfile для CPU, runtime.MemStats для памяти, trace.Start для трассировки, включая flight recording с ограничением по размеру и времени. Все инструменты направлены на безопасную и эффективную конкурентность без гонок данных.
Комментарии (180)
Go-конкурентность проста для старта, но сложна для безопасной эксплуатации в проде: утечки горутин, отсутствие встроенной отмены и трудности с отладкой — частые проблемы. Опытные разработчики обходят их через context, WaitGroup и избегают каналов там, где они не нужны. Не используйте каналы повсеместно — многие профессионалы Go редко применяют их, предпочитая последовательный код и вводя конкуренцию только при превышении SLO. Перед запуском горутины обязательно определите, как она будет остановлена — утечки горутин в проде трудно отлаживать и требуют перезапуска процесса. Используйте context для отмены, но помните: отмена требует сотрудничества горутины — Go не поддерживает прерывание, так как не имеет исключений. Простые шаблоны go+chan из учебников часто небезопасны — реальные приложения требуют хаков для управления жизненным циклом горутин. Избегайте сложных графов операций в Go — без generics и completable futures это сложно; альтернативы вроде goyek помогают, но не решают фундаментальные ограничения. Go-каналы не эквивалентны Haskell's STM — отсутствие транзакционной памяти снижает абстрактную мощность, а поведение при закрытии канала дважды — произвольное и непредсказуемое. Java с виртуальными потоками и структурированной конкурентностью предлагает более надёжный и безопасный подход, чем Go, особенно с учётом отсутствия memory safety в Go. Erlang/BEAM и Elixir имеют более зрелую модель акторов с распределённой обработкой, устойчивостью к сбоям и поддержкой межмашинной коммуникации, чего Go не имеет. Kotlin coroutines и структурированная конкурентность решают проблему отмены и управления жизненным циклом более элегантно, чем Go-контекст. Haskell позволяет писать асинхронный код как синхронный — без прыжков между сообщениями, что делает логику проще для понимания, чем в Go-каналах. Julia предлагает более продвинутую систему потоков: M:N, структурированная конкурентность, распределённые вычисления и безопасные атомарные операции в стандартной библиотеке. Опытные разработчики Go часто избегают mutex и каналов в PR — любое использование этих механизмов воспринимается как признак ошибки. Для сложных задач с очередями и диспетчеризацией лучше выносить конкурентность в отдельные менеджеры — логика остаётся линейной, что упрощает аудит и отладку. Go-конкурентность — это магия для старта, но требует глубокого понимания для продакшена: утечки, отмена и отладка — главные боли, которые не решаются синтаксисом. Каналы в Go — это просто буферизованные очереди; небуферизованные каналы создают rendezvous-синхронизацию, что отличает их от mvar и требует осознанного применения.
Futurelock: A subtle risk in async Rust 🔥 Горячее 💬 Длинная дискуссия
В статье описывается концепция "futurelock" — особый тип дедлока, возникающий в асинхронном Rust, когда ресурс, принадлежащий Future A, требуется для Future B, а задача, ответственная за оба Future, перестала опрашивать A. Oxide обнаружила эту проблему в проекте omicron.
Пример демонстрирует программу, которая надежно замирает: фоновая задача захватывает блокировку на 5 секунд, основная задача использует tokio::select! для ожидания двух конкурентных Future. Один Future ждет блокировку, другой — таймаут. Когда таймаут срабатывает, создается новый Future, который также ждет блокировку, но задача больше не опрашивает исходный Future, что приводит к дедлоку.
Комментарии (217)
- Проблема в том, что
tokio::select!отменяет все futures, кроме одного, и это может привести к deadlock, если оставленные в стороне future владеет ресурсом (например, MutexGuard). - Возможность отмены future в Rust в целом не определена, и это приводит к тому, что
tokio::select!ведёт себя непредсказуемо для разработчика, что может привести к утечке ресурсов или deadlock. - В отличие от других языков, где отмена future ведёт к её уничтожению, в Rust модель владения и заимствования не позволяет гарантировать, что ресурсы будут очищены.
- Это приводит к тому, что в Rust нет способа защититься от таких ситуаций на уровне языка, и единственное, что может сделать разработчик - это не использовать
select!и вместо этого вручную управлять состоянием. - В конце концов, это делает async Rust более сложным в использовании, чем он должен бы быть, и создаёт уязвимости, которые не существовали бы в других моделях параллельности.