Комментарии 14
Rust вырос в топовый язык, сформированный аккуратным дизайном
До первого unsafe
Спасибо за исследование
Шикарный разбор, автору респект. Позволю пару советов дать, надеюсь они ему помогут.
Абсолютно правильно у автора, свойства cancel safety нет в системе типов Rust, и компилятор тут молчит. Но в экосистеме есть изящный архитектурный трюк, как запретить прерывать фьючу посредине, не плодя тяжелые tokio::spawn. Можно использовать паттерн мьютекса на владение фьючей или кастомные расширения. Но еще проще — гарантировать неделимость операции через специальный тип-токен или использовать крейт non_cancellable (или написать свой микро-мини-раннер). Если логика должна гарантированно добежать до конца, мы можем завернуть ее в poll_fn, где на уровне состояния рантайма игнорируется запрос на отмену, либо передавать управление в объект, чей Drop гарантированно допишет критические данные в фоновом таске. Собственно совет для промпта: Обучить модель паттерну «Linear Types» (линейных типов), где деструкция объекта (Drop) обязана выполнить финализацию, либо использовать явную конечную машину (FSM), где незавершенное состояние при дропе паникует в дебаге. Если ИИ будет знать, что за недоделанный контракт его код уронит приложение в тестах, он перестанет писать небезопасные await-точки. Подсказка номер 2. Ловим “Allocator на стеке” автоматически через clippy. Автор упомянул, что Box::new([0u8; 1024*1024]) сначала выделяет память на стеке (особенно в дебаге), и это больно. Он решает это через vec!..into_boxed_slice(). Чтобы не вылавливать это глазами и не тратить час на дебаг макросов, можно включить специфический линт Clippy, который ИИ-кодеры часто игнорируют. В clippy.toml (или прямо в коде) стоит прописать:
#![deny(clippy::large_stack_frames)] #![deny(clippy::large_stack_arrays)]
Если у автора в проекте размер стека функции жестко ограничен, clippy::large_stack_frames выдаст ошибку компиляции еще до запуска тестов, если модель попытается выпихнуть мегабайт в локальные переменные. Это переведет категорию №7 из разряда заметно при запуске в разряд не скомпилировалось, что сэкономит время. Подсказка номер 3. Избавляемся от “Semver Hazard” с помощью cargo-public-api Шестая категория ошибок автора ( blanket impl в публичном API, который ломает зависимые крейты при обновлении) — это классическая головная боль авторов библиотек. Модели обожают широкие жесты вроде impl<T: Display> Bar for T. Чтобы застраховать свой монолит или внутренние библиотеки от таких сюрпризов, в CI стоит добавить утилиту cargo-public-api. Она умеет: фиксировать всё публичное API крейта в текстовый файл-слепок (snapshot). Сравнивать текущую сборку с мастером. Если Claude или GPT втихаря добавят blanket impl, cargo-public-api мгновенно подсветит это в диффе CI как изменение контракта, даже если сам крейт идеально компилируется. Это избавит от ситуации, когда у нас всё ок, а у потребителей через три месяца всё упало. Автору удачи.
В целом это всё прекрасно, отличные советы, но я одного не пойму. Все эти тесты, все эти ограничения, тонны проверок компилятором, линтеров, тонны врапперов для промптов и так далее - всё это выглядит так странно, неужели все думают что после этого ллмки начнут писать рабочий код? Это как если бы для функции, возвращающей рандомные числа перед тем, как результат отдать клиенту, написали кучу ограничений на разные диапазоны и чтоб она в цикле продолжала генерить до тех пор, пока не сгенерит число, не попадающее в эти ограничивающие диапазоны.
Borrow checker рассуждает о графе заимствований во времени.
А если LLM на вход подавать еще AST, HIR, MIR?
Полезное, спасибо!
А теперь, "claude, сделай из этой статьи и комментариев SKILL rust-coder"
Спасибо за статью! сам растом пока ещё не пользовался, только планирую. Очень здорово что вы поделились местом где лежат грабли и советами как обойти. С вашего позволения хочу предложить свои 5 копеек:
Мне кажется, часть этих проблем можно снижать не только промптами и CI, но и устройством самой кодовой базы.
Если коротко:
Делать в коде “липкие комментарии” в узловых местах. Не комментарии вида “тут создаём кеш”, а предупреждения: “этот кусок связан с таким-то внешним контрактом, не упрощать, не переносить, не менять порядок операций”.
Комментарий должен быть не источником истины, а навигатором: “смотри такой-то контракт / blueprint / ADR / тест”. Тогда агент, который пришёл менять локальный кусок кода, получает шанс добрать нужный внешний контекст.
Важные правила лучше фиксировать в нескольких слоях: документ → схема/тип → тест → runtime check → sticky comment рядом с местом риска. Тогда LLM не просто “прочитала пожелание”, а попала в систему ограничителей.
Нужны не только промпты “напиши правильно”, а проектные skills/runbooks: если меняешь async-код — проверь отмену; если меняешь публичный API — проверь совместимость; если трогаешь кеш/транзакции/lock — проверь порядок владения и побочные эффекты.
Агенту полезно давать не весь репозиторий сразу, а маршрутизированный контекст: какие документы канонические, какие решения уже приняты, какие места нельзя “улучшать”, какие инварианты обязательны.
После реализации агент должен не просто прогнать тесты, а сделать контрактный аудит: какие инварианты затронуты, какие sticky comments обновлены, какие рисковые места проверены.
То есть проблема не только в том, что LLM не видит контекст. Проблема в том, что контекст часто не оформлен как инженерная система. Если превратить его в контракты, локальные маяки, тесты и обязательные проверки, часть “слепых зон LLM” становится обычным управляемым риском.
И все таки еще раз спасибо за статью! В эпоху вайбкодинга, такие материалы от реальных разработчиков на вес золота!
Я заставил LLM писать Rust полгода
И они это вам еще припомнят.
Интересно будет через год прочесть вашу статью о том, как теперь модели лучше ловят нюансы.
pub struct Cache {
inner: Arc<Mutex<HashMap<String, Vec<u8>>>>,
}
impl Cache {
pub async fn get(&self, key: &str) -> Option<Vec<u8>> {
let guard = self.inner.lock().unwrap();
guard.get(key).cloned()
}
}Вот тут не понял, почему будет deadlock и clippy ругается. Тут же взятие блокировки у std Mutex происходит в рамках одного poll и на время, которое нужно для клонирования данных, верно? В любом случае, даже в tokio документации сказано - используйте синхронную блокировку за редким исключением - и в этом методе можно просто убрать async.
<удалено>
Промпты, которые реально работают
За полгода я вывел несколько шаблонов, которые статистически снижают количество ошибок в моём бенчмарке. Делюсь.
Вместо шаблонов и для Anthropic Claude в CLAUDE.md и для Cursor можно создать правила, которым и один и другой должен следовать.
Соблюдение правил можно внедрить в Hooks, который будет обязательно проверять код, прежде чем мерджить на Remote.
Для соблюдения сигнатур, если кода много, можно создать MCP сервер. и добавить в курсор "/" команду работы с кодом, следуя MCP сигнатурам функции вашего проекта.

Я заставил LLM писать Rust полгода. Вот что они стабильно ломают