Промты для Rust: как пройти borrow checker и написать системный код уровня senior без наставника

Промты для Rust: как пройти borrow checker и написать системный код уровня senior без наставника

Rust третий год подряд занимает первое место в опросе Stack Overflow Developer Survey как «самый любимый» язык, но он же стабильно попадает в топ «самых пугающих» для новичков. Причина одна: компилятор. Borrow checker, lifetimes, Send/Sync, unsafe-контракты — всё это требует ментальной модели, которой нет у разработчика, пришедшего из Python или Go.

Хорошая новость: LLM научились объяснять ошибки компилятора лучше, чем половина статей на Medium. Плохая новость: без правильных промтов вы получите общие советы вроде «просто используйте clone()» — что убивает производительность и не учит ничему.

Ниже — 12 промтов, которые я отобрал из практики работы с Rust-кодом: от разбора E0502 до написания FFI-обёрток и бенчмарков на Criterion. Каждый промт — с задачей, формулировкой и примером результата. Промты работают с ChatGPT, Claude, Gemini и локальными моделями уровня Qwen2.5-Coder-32B.

Базовый уровень: вход в язык без боли

1. Расшифровка ошибок borrow checker на человеческом языке

Задача: компилятор выдаёт error[E0502]: cannot borrow ... as mutable because it is also borrowed as immutable — и вы не понимаете, где именно нарушили правила.

Промт:

Ты — ментор по Rust. Разбери ошибку компилятора по шагам:
1) Что именно запрещает borrow checker в этом коде;
2) Какая ментальная модель за этим стоит (ownership, заимствование);
3) Три варианта исправления — от самого простого до самого идиоматичного;
4) Какой вариант выберет опытный Rust-разработчик и почему.

Вот код и ошибка:
<код>
<ошибка целиком>

Пример результата: модель объясняет, что iter() создаёт неизменяемый займ на Vec, а внутри цикла вы пытаетесь вызвать push() — это мутабельный займ. Предлагает: (а) собрать индексы в отдельный Vec<usize> и мутировать после цикла; (б) использовать retain(); (в) переписать через drain_filter/extract_if. Третий вариант — идиоматичный.

2. Объяснение lifetimes через реальные аналогии

Задача: синтаксис 'a, 'static и элизия выглядят как магия.

Промт:

Объясни lifetimes в Rust на примере функции, возвращающей ссылку на поле структуры.
Покажи:
- почему без аннотации код не компилируется (приведи точный текст ошибки);
- что означает 'a в сигнатуре fn longest<'a>(x: &'a str, y: &'a str) -> &'a str;
- разницу между 'static и временем жизни переменной;
- 2 антипаттерна, которые новички делают с lifetimes.
Без воды, с рабочим кодом.

Пример результата: модель строит таблицу «что живёт дольше» и показывает, что 'static — это не «навсегда в памяти», а «живёт всю программу», поэтому строковые литералы "hello" имеют тип &'static str.

3. Тренажёр на ownership: 10 задач с проверкой

Задача: нужна практика, а не теория.

Промт:

Сгенерируй 10 задач по ownership и borrowing в Rust, от простого к сложному.
Формат каждой задачи:
- фрагмент кода с пропусками или ошибкой;
- вопрос: скомпилируется ли, что выведет;
- правильный ответ с объяснением (свёрнут в <details>).
Покрой: move, copy, reborrow, shadowing, drop order, временные значения.

Пример результата: вы получаете набор в стиле Rustlings, но адаптированный под ваши пробелы. Один из примеров: почему let s2 = s1; println!("{}", s1); не компилируется для String, но компилируется для i32 — и что такое трейт Copy.

Средний уровень: асинхронность, тесты, макросы

4. Асинхронный код на tokio: от блокирующего к неблокирующему

Задача: переписать синхронный парсер/загрузчик в async без ошибок Send.

Промт:

Перепиши этот синхронный код на async/await с tokio.
Требования:
- не держать MutexGuard через .await (объясни, почему это ошибка);
- использовать tokio::sync::Mutex там, где нужно, и std::sync::Mutex — где нет;
- покажи, как проверить, что future: Send, и как читать ошибку "future is not Send";
- добавь tokio::time::timeout для внешнего вызова.

<код>

Пример результата: модель показывает классическую ловушку — std::sync::MutexGuard не является Send, поэтому удержание его через .await ломает компиляцию. Предлагает обернуть блок в {}, чтобы гард дропнулся до точки ожидания.

5. Тесты: unit, integration, property-based

Задача: покрыть модуль тестами, включая крайние случаи.

Промт:

Для модуля ниже напиши:
1) unit-тесты в #[cfg(test)] mod tests;
2) integration-тест в tests/;
3) property-based тест на proptest с инвариантом (например, encode(decode(x)) == x);
4) doctest в комментарии к публичной функции.
Используй assert_eq!, assert_matches!, should_panic с проверкой сообщения.

<код>

Пример результата: помимо очевидных кейсов, LLM добавляет тест на переполнение u32, пустой ввод и Unicode-границы — то, что обычно забывают.

6. Декларативные макросы: от macro_rules! до разбора ошибок

Задача: написать макрос, который принимает разное число аргументов, и понять, как отлаживать его раскрытие.

Промт:

Напиши macro_rules!-макрос `hashmap!`, который принимает пары ключ=>значение.
Объясни:
- как работает фрагмент $($k:expr => $v:expr),*;
- как посмотреть раскрытие через `cargo expand`;
- почему макрос не видит типы и как это ограничивает;
- когда лучше procedural macro, а не macro_rules!.

Пример результата: вы получаете рабочий макрос и понимание, что cargo expand — это первый инструмент отладки любого макроса.

7. Оптимизация: профилирование и горячие точки

Задача: код работает медленно, но непонятно где.

Промт:

Помоги найти узкие места в этом Rust-коде.
1) Предложи план профилирования: perf, cargo flamegraph, criterion.
2) Укажи 3 места, которые вероятно медленные, и почему.
3) Предложи оптимизации: аллокации, итераторы вместо индексации, SmallVec, capacity.
4) Напиши бенчмарк на criterion, чтобы подтвердить улучшение.

<код>

Пример результата: типичный ответ — заменить Vec::push в цикле на with_capacity(n), убрать clone() в горячем пути, использовать iter().sum() вместо ручного индекса (LLVM лучше векторизует).

Продвинутый уровень: unsafe, FFI, документация

8. Безопасная обёртка над unsafe-блоком

Задача: есть unsafe код, нужно доказать его корректность и изолировать.

Промт:

Проверь этот unsafe-блок на соответствие правилам Rust (Rustonomicon, раздел "Soundness").
Для каждого unsafe-действия укажи:
- какой инвариант должен выполняться;
- кто его гарантирует;
- что будет при нарушении (UB или паника);
- как переписать так, чтобы unsafe был минимальным и обёрнут в безопасный API с #[doc] о безопасности.

<код>

Пример результата: модель указывает, что разыменование сырого указателя требует проверки на null и выравнивания, и предлагает обёртку struct Wrapper с unsafe fn внутри и безопасным публичным методом снаружи.

9. FFI: связка Rust с C без утечек

Задача: вызвать C-библиотеку из Rust и не уронить процесс.

Промт:

Напиши безопасную FFI-обёртку над C-функцией:
int process(const char* input, char** output, size_t* out_len);

Требуется:
- extern "C" блок с #[link(name = "...")];
- обработка NULL и кодов возврата;
- преобразование CString <-> String без утечек;
- освобождение памяти, выделенной на стороне C, через правильную функцию;
- пример вызова из Rust с обработкой ошибок.

Пример результата: вы получаете код, где CString::into_raw() и CString::from_raw() используются симметрично, а #[repr(C)] стоит там, где нужно.

10. Документация крейта: doc-comments и пример из README

Задача: превратить код в публикуемый крейт.

Промт:

Напиши документацию для публичного API этого модуля:
- для каждой публичной функции — /// с разделом "# Errors" и "# Panics";
- для крейта — //! с примером из README;
- добавь doctest, который реально компилируется;
- объясни, почему "# Examples" в doc-comment прогоняются `cargo test`.

Пример результата: вы получаете документацию в стиле serde или tokio, где примеры в комментариях являются одновременно тестами.

11. Разбор чужого крейта: как читать исходники

Задача: понять, как устроен популярный крейт, чтобы применить паттерн у себя.

Промт:

Объясни архитектуру крейта <название> на примере его исходников.
1) Какие модули публичные, какие приватные и почему.
2) Как организованы трейты и почему они разделены именно так.
3) Какой паттерн используется для конфигурации (builder, typestate, newtype).
4) Что бы я узнал, если бы читал этот код впервые — с чего начать.

Пример результата: разбор reqwest показывает паттерн builder, разделение sync/async API и почему Client дешево клонировать (Arc внутри).

12. Переход с другого языка: персонализированная карта пробелов

Задача: вы знаете Python/Go/C++ и хотите быстро выйти на продуктивный Rust.

Промт:

Я пишу на <язык> N лет. Составь карту перехода на Rust:
- что переносится напрямую (итераторы, generics);
- что придётся переучить (GC vs ownership, exceptions vs Result);
- 5 типичных ошибок мигрантов из <язык>;
- мини-проект на 200 строк, который закрепит ключевые концепции;
- список глав из The Rust Programming Language, которые можно пропустить, зная <язык>.

Пример результата: для Go-разработчика модель выделяет, что Result<T,E> заменяет (T, error), а отсутствие GC требует думать о lifetimes явно; предлагает написать CLI-счётчик слов с парсингом аргументов через clap.

Как использовать эти промты правильно

Несколько правил, которые я вывел за годы работы с LLM на Rust-коде:

  • Всегда давайте полный текст ошибки компилятора. cargo build 2>&1 | pbcopy — ваш друг. Модель по тексту ошибки определяет точную строку и тип проблемы.
  • Просите объяснить, а не только починить. Промт «исправь» даёт костыль, промт «объясни инвариант и предложи три варианта» — понимание.
  • Проверяйте unsafe-код дважды. LLM иногда предлагает код, который компилируется, но нарушает soundness. Всегда сверяйтесь с Rustonomicon.
  • Используйте cargo clippy -- -D warnings после генерации. Многие идиоматические улучшения Clippy подскажет сам.

Rust — это язык, где компилятор уже ваш ментор. ИИ-ассистент с правильными промтами сжимает путь от «борюсь с borrow checker» до «пишу системный код с уверенностью» в разы. Если хотите системно прокачать эту связку — посмотрите материалы по системному программированию на asibiont.com: там разбираются концепции, которые эти промты помогают закрепить на практике.

← Все статьи

Комментарии

Читайте также

Курс Time Series (анализ временных рядов): как освоить прогнозирование от Prophet до LSTM с помощью AI на Asibiont

26 июля 2026

Real-time системы (WebSockets, WebRTC): как перестать тормозить и начать жить в реальном времени

7 октября 2026

Vibe Coding в 2026 году: полное руководство разработчика по созданию AI-First приложений

10 июля 2026

Индия начинает платить за приложения: почему разработчикам уже пора на этот рынок

1 августа 2026

Vibe Coding промты для бизнеса: лендинг, CRM, дашборд за 1 промт — готовые решения без программистов

10 июня 2026

Как построить инвестиционный фонд с нуля: личный опыт после курса Private Equity & Venture Capital — инвестиции и сделки на asibiont.com

25 июня 2026

Django и FastAPI — Python Web: Как выбрать стек в 2026 году и получить x2 к зарплате

13 июля 2026

Как AI-агент ASI Biont автоматизирует Wildberries: интеграция за 5 минут через API

16 июля 2026

Open Source Contribution: Как AI-тьютор Asibiont сокращает путь от новичка до maintainer с 18 до 6 месяцев

15 июля 2026