惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Твой код на Rust компилируется, проходит тесты и является UB. Ты просто об этом не знаешь
vibecodingai · 2026-05-09 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение6 мин

Охват и читатели853

Мнение

Если взять случайный крейт с crates.io, поставить на него Miri и подождать минут пять, шанс увидеть красное сообщение про undefined behavior где-то в зависимостях стремится к единице. Чаще всего виноват не автор хитрого unsafe-блока ради скорости, а вполне обычная библиотека, которой пять лет, у которой звёзд на гитхабе больше, чем у твоего пет-проекта строчек кода, и которая всё это время спокойно лежит в продакшене.

Самое неприятное в этой истории то, что компилятор ничего не скажет. Тесты пройдут. Бенчмарки покажут красивые наносекунды. А потом LLVM 19 обновится до LLVM 20, поменяет один проход оптимизации, и твой сервис начнёт ронять прод по понедельникам. Чтобы понять, почему так происходит, придётся залезть в три темы, которые в обычной жизни Rust-разработчика не встречаются: pointer provenance, Stacked Borrows и пришедшую им на смену Tree Borrows.

Указатель это не адрес

Самое вредное заблуждение, которое тащится в Rust из C, звучит так: указатель это число, представляющее адрес в памяти. С точки зрения процессора так и есть. С точки зрения компилятора уже лет двадцать как нет, просто в C про это стесняются говорить вслух, а в Rust решили оформить явно.

У каждого указателя в модели Rust помимо адреса есть невидимый ярлык, называется provenance. Это по сути идентификатор аллокации, из которой указатель родился, плюс набор разрешений на доступ. Когда ты делаешь Box::new(42), рантайм возвращает тебе не просто адрес, а адрес плюс свежий ярлык, привязанный именно к этому боксу. Когда ты кастуешь &mut T в *mut T, ярлык наследуется. Когда ты складываешь два числа и пытаешься объявить результат указателем через transmute, ярлыка у получившейся штуки нет, и любое разыменование такого указателя является UB, даже если по битам адрес совпадает с тем, что вернул аллокатор пять строк назад.

Проверить это руками просто:

fn main() {
    let a = [1u8, 2, 3, 4];
    let b = [5u8, 6, 7, 8];
    let pa = a.as_ptr();
    let pb = b.as_ptr();
    let offset = pb as isize - pa as isize;
    let forged = unsafe { pa.offset(offset) };
    unsafe { println!("{}", *forged); }
}

На любом нормальном процессоре forged будет указывать ровно туда же, куда pb. Программа что-то напечатает. Скорее всего пятёрку. Запусти под Miri, и увидишь сообщение про trying to retag from a pointer that was not derived from this allocation. С точки зрения железа всё корректно, с точки зрения модели памяти Rust ты использовал ярлык от массива a, чтобы залезть в массив b, а это запрещено независимо от того, что числа сошлись.

Смысл провенанса в том, чтобы компилятор мог делать алиасинг-анализ без ограничений языка C. В C спецификация говорит: если два указателя на разные объекты, они не алиасят, кроме случаев. Случаев много, они громоздкие, и restrict помогает редко. В Rust спецификация говорит: ярлыки разные, значит указатели не алиасят, точка. Это позволяет помечать почти все ссылки атрибутом noalias на уровне LLVM, и оптимизатор имеет право переставлять загрузки и записи как ему удобно.

Stacked Borrows как формальная игра в реборроу

Долгое время не существовало вообще никакой формальной модели, описывающей, какой код Rust считается корректным на уровне unsafe. Команда rustc писала компилятор, опираясь на интуицию, LLVM делал оптимизации по своим правилам, а пользователи писали unsafe-блоки, надеясь, что оба этих мира между собой договорятся. В 2018 году Ральф Юнг предложил Stacked Borrows, и с этого момента у Rust появилась хотя бы рабочая теория, которую можно проверять на машине.

Идея устроена так. К каждому байту памяти мысленно прицеплен стек тегов. Когда ты создаёшь новую ссылку на этот байт, на стек кладётся новый тег. Когда ты используешь ссылку, проверяется, что её тег где-то в стеке есть, и все теги выше неё снимаются. Если тега в стеке не оказалось, это UB.

Из этой простой механики вытекает почти вся семантика заимствований, включая ту, которую ты привык получать от borrow checker, плюс куча правил для unsafe, которых borrow checker не видит. Например, классика:

fn main() {
    let mut x = 42;
    let r1 = &mut x;
    let raw = r1 as *mut i32;
    let r2 = &mut *r1;
    *r2 = 7;
    unsafe { *raw = 13; }
    println!("{}", x);
}

Borrow checker этот код пропускает: raw это сырой указатель, его время жизни никого не волнует, r2 валидный реборроу от r1. Программа компилируется, запускается, печатает 13. Под Miri ловится UB. Объяснение в терминах стека: создание r2 положило новый тег поверх тега raw, поэтому, когда ты пишешь через raw, его тег уже не на вершине, а правила Stacked Borrows требуют для записи, чтобы тег был в стеке и выше него ничего не лежало.

Любимый сценарий, в котором это превращается в баг прода, это передача сырого указателя в C-библиотеку. Ты честно создаёшь &mut, кастуешь, передаёшь. Где-то рядом другой &mut от того же объекта живёт в Rust-коде, и компилятор имеет полное право предположить, что C через сырой указатель туда не полезет, потому что ярлык не тот. Ассемблер, сгенерированный из таких предположений, начинает кешировать значения в регистрах, и привет.

Почему пришлось выключать noalias и причём тут LLVM

В районе 2014 года rustc впервые включил noalias для &mut ссылок. Через несколько недель выяснилось, что в LLVM есть баги, которые срабатывают только на коде с большим количеством noalias-параметров, потому что C-компиляторы такой код почти не генерируют. Атрибут пришлось выключить. Включили обратно через пару лет, поймали следующий слой багов, выключили снова. Эта история с переменным успехом продолжалась примерно до 2021 года, и каждый раз rustc находил в LLVM что-то новое, что Clang за двадцать лет не нашёл, потому что человек на C просто не пишет в стиле «двадцать ссылок без алиасинга подряд».

Побочный эффект для всего сишного мира получился приятный: те же оптимизации заработали корректнее в Clang, и restrict стало можно использовать без страха, что компилятор споткнётся. Rust здесь сыграл роль адского стенда нагрузочного тестирования для алиасинг-анализа в LLVM, и до сих пор играет.

Tree Borrows и почему Stacked Borrows оказалось слишком строгим

Stacked Borrows красивая модель, но у неё есть проблема. Она запрещает паттерны, которые широко встречаются в реальном unsafe-коде и при этом интуитивно выглядят корректными. Самый болезненный пример это interior mutability через сырые указатели и работа со стандартными коллекциями.

use std::cell::Cell;

fn main() {
    let c = Cell::new(0i32);
    let p = &c as *const Cell<i32> as *mut i32;
    unsafe { *p = 1; }
    println!("{}", c.get());
}

По интуиции код корректен: Cell для того и существует, чтобы внутрь можно было писать через общую ссылку. По Stacked Borrows здесь нюансы с тем, как именно был получен p, и разные версии Miri разные версии этого кода ругают по-разному. Накопилась библиотека таких пограничных случаев, и стало понятно, что модель надо чинить.

Tree Borrows это работа Невена Вильмена, защищённая в 2023 году. Вместо стека тегов на каждый байт строится дерево, корнем которого является исходная аллокация, а детьми идут все производные ссылки и указатели. У каждого узла есть состояние: Reserved, Active, Frozen, Disabled. Переходы между состояниями описывают, что разрешено делать с памятью через данный тег, и что происходит с потомками, когда родителя используют для чтения или записи.

Эта модель строго слабее Stacked Borrows на куче существующих unsafe-паттернов и одновременно строго сильнее на тех местах, где Stacked Borrows был дырявым. Самое практичное следствие: код вокруг UnsafeCell, который раньше ругался в Miri без явной вины автора, под Tree Borrows проходит. Включить в Miri можно через MIRIFLAGS=-Zmiri-tree-borrows.

Что с этим делать сегодня

Если ты пишешь только safe Rust и не используешь unsafe, можно условно расслабиться. Условно потому, что любая твоя зависимость, у которой внутри есть unsafe impl Send или ручная работа с указателями, может протащить UB к тебе. Поэтому простой совет звучит так: раз в спринт прогоняй тесты под Miri и Tree Borrows.

cargo +nightly miri test
MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test

Если тесты под Miri идут на порядок дольше обычных, это нормально, Miri это интерпретатор, а не нативный код. Но они находят такое, что не находит ни санитайзер, ни fuzz, ни ручной ревью.

Для авторов библиотек есть пара более жёстких правил, которые стоит держать в голове. Никогда не получай ссылку и сырой указатель из одного и того же &mut так, чтобы они оба пережили друг друга. Если нужен сырой указатель, который будет жить долго, бери его до создания всех ссылок и не делай реборроу. Используй UnsafeCell для всего, во что планируешь писать через общий доступ, даже если кажется, что рядом нет ни одной ссылки. И не пытайся восстановить указатель из числа через as, если у тебя нет провенанса под рукой, для этого с недавних пор есть with_exposed_provenance, и это не косметика, а единственный задокументированный способ сделать такое без UB.

Финальная мысль

Главное, что меняется в голове после знакомства с провенансом и Tree Borrows, это отношение к unsafe. Unsafe в Rust часто подаётся как «здесь компилятор тебе верит». На самом деле всё наоборот: в unsafe компилятор тебе верит ровно по тем правилам, которые описаны в модели памяти, и эти правила строже, чем в C. Цена этой строгости агрессивные оптимизации, которых нет нигде больше. Каждый раз, когда твой бенчмарк показывает, что Rust на десять процентов быстрее аналогичного C, где-то под капотом сработал noalias, который смог сработать только потому, что Stacked или Tree Borrows гарантировал отсутствие алиасинга.

Так что когда видишь в выводе Miri незнакомое слово retag, не закрывай терминал. Это компилятор показывает, что между твоим представлением о памяти и его представлением начали расходиться чертежи, и через пару релизов LLVM это разойдение станет видно невооружённым глазом, прямо в проде.


Люблю Rust, пишу на нём и разбираю код так, чтобы сложные вещи становились понятнее — подписывайтесь: t.me/rust_code

Спасибо за внимание, пишите в комментах, плиз ваши замечания!