Borrow checker — это не баг, а фича. Но когда компилятор в сотый раз отвергает ваш код с E0502, хочется сдаться. Асинхронный Rust добавляет ещё один уровень сложности: Pin, Send, 'static и вездесущие лайфтаймы. Хорошая новость: с правильными промтами для LLM вы не просто получаете работающий код, но и учитесь думать как компилятор.
Эта статья — не очередная подборка «10 промтов для Rust». Это набор проверенных шаблонов, которые я собрал за несколько лет работы с Rust в продакшене и с AI-ассистентами. Каждый промт решает конкретную боль: от объяснения ошибки заимствования до проектирования асинхронного пайплайна на Tokio. Вы можете копировать их как есть и адаптировать под свою задачу.
Все примеры кода компилируются с Rust 1.80+ (стабильный канал). Для асинхронных примеров используется Tokio 1.38+. Ссылки на официальную документацию прилагаются.
1. Объяснение ошибки заимствования без воды
Задача: Быстро понять, почему компилятор ругается на cannot borrow as mutable, и получить исправленный код.
Промт:
Ты — эксперт по Rust. Объясни ошибку компилятора, не используя общих фраз. Вот код:
[вставьте код]
Вот сообщение об ошибке:
[вставьте полный вывод cargo check]
Сделай следующее:
1. Назови точную причину: где именно происходит конфликт заимствований.
2. Предложи минимальное исправление (изменить владение, клонировать, использовать Rc<RefCell> или перестроить логику).
3. Покажи исправленный код целиком.
4. Объясни, как избежать подобной ошибки в будущем, на примере этого кода.
Пример использования:
Вы пишете функцию, которая берёт &mut Vec<i32> и внутри цикла пытается передать элемент в другую функцию, которая тоже хочет &mut. Компилятор говорит E0499. Промт заставит LLM показать, что проблема в одновременном заимствовании, и предложит использовать split_at_mut или временную переменную.
Почему работает: LLM видит конкретный контекст и учится на реальной ошибке, а не даёт абстрактные советы.
2. Генерация кода с явными лайфтаймами
Задача: Написать функцию, которая принимает ссылки и возвращает ссылку, не нарушая правила.
Промт:
Напиши на Rust функцию, которая принимает два среза `&[T]` и возвращает самый длинный из них. Используй явные лайфтаймы. Добавь doc-комментарий с примером вызова. Объясни, почему лайфтайм указан именно так.
Пример вывода:
/// Возвращает самый длинный срез из двух.
///
/// # Пример
/// ```
/// let a = [1, 2, 3];
/// let b = [4, 5];
/// assert_eq!(longest(&a, &b), &[1, 2, 3]);
/// ```
pub fn longest<'a, T>(x: &'a [T], y: &'a [T]) -> &'a [T] {
if x.len() > y.len() { x } else { y }
}
Совет: После генерации всегда запускайте cargo clippy — иногда LLM предлагает избыточные лайфтаймы.
3. Перевод синхронного кода в асинхронный
Задача: Превратить блокирующий код в асинхронный с помощью Tokio.
Промт:
Преобразуй следующий синхронный код в асинхронный, используя Tokio. Замени блокирующие вызовы на асинхронные аналоги (например, std::fs на tokio::fs). Добавь необходимые .await и обработку ошибок. Покажи полный код с зависимостями в Cargo.toml.
Синхронный код:
[вставьте код]
Пример: Синхронное чтение файла заменяется на tokio::fs::read_to_string, а std::thread::sleep — на tokio::time::sleep. LLM также напомнит про #[tokio::main].
Источник: Tokio Tutorial.
4. Отладка Send и Sync в асинхронном коде
Задача: Понять, почему future не реализует Send, и исправить это.
Промт:
Мой асинхронный код не компилируется с ошибкой: `future cannot be sent between threads safely`. Вот код:
[вставьте код]
Объясни:
1. Какие типы внутри future не являются Send?
2. Почему это происходит (например, Rc, RefCell, raw pointers)?
3. Как переписать код, чтобы future стал Send (заменить на Arc<Mutex>, использовать tokio::sync::Mutex и т.д.)?
4. Покажи исправленный код.
Пример: Часто проблема в Rc или в захвате &mut через .await. LLM предложит заменить Rc на Arc, а RefCell на Mutex.
Ссылка: Rust Async Book: Send and Sync.
5. Проектирование структуры с Arc<Mutex<T>> без дедлоков
Задача: Создать разделяемое состояние для многопоточного сервера.
Промт:
Спроектируй структуру для разделяемого состояния в асинхронном веб-сервере на Tokio. Используй Arc<Mutex<HashMap<String, String>>>. Напиши пример функции, которая добавляет и читает данные. Объясни, как избежать дедлоков при удержании блокировки через .await. Покажи код с комментариями.
Пример вывода:
use std::sync::Arc;
use tokio::sync::Mutex;
use std::collections::HashMap;
type Db = Arc<Mutex<HashMap<String, String>>>;
async fn insert(db: Db, key: String, value: String) {
let mut map = db.lock().await;
map.insert(key, value);
// блокировка автоматически отпускается при выходе из области видимости
}
Совет: Всегда используйте tokio::sync::Mutex в асинхронном коде, если блокировка удерживается через .await.
6. Написание тестов для асинхронных функций
Задача: Сгенерировать тесты с #[tokio::test].
Промт:
Напиши юнит-тесты для асинхронной функции [имя функции]. Используй #[tokio::test]. Проверь успешный сценарий, ошибку и граничные случаи. Добавь моки для зависимостей, если нужно. Покажи полный код тестов.
Пример: Для функции, которая делает HTTP-запрос, LLM предложит использовать mockito или wiremock для мокирования ответов.
Источник: Tokio Testing.
7. Оптимизация производительности асинхронного кода
Задача: Ускорить существующий асинхронный код.
Промт:
Проанализируй этот асинхронный код на Rust и предложи оптимизации:
[вставьте код]
Учти:
- Избегание лишних аллокаций
- Использование буферизации (BufReader)
- Параллельное выполнение через join! или FuturesUnordered
- Замена блокирующих операций
Покажи улучшенную версию и объясни каждое изменение.
Пример: Замена последовательных .await на tokio::join! для независимых задач.
8. Работа с Pin и !Unpin типами
Задача: Понять, когда нужен Pin, и написать код с самоссылающимися структурами.
Промт:
Объясни, зачем нужен Pin в Rust, на примере асинхронного кода. Напиши пример структуры, которая требует Pin, и покажи, как с ней работать. Используй std::pin::Pin. Не углубляйся в unsafe, если можно обойтись безопасным кодом.
Пример: LLM покажет, что async fn возвращает !Unpin future, и как Box::pin помогает.
Ссылка: std::pin.
9. Обработка ошибок в асинхронном Rust
Задача: Правильно комбинировать Result, ? и кастомные ошибки.
Промт:
Напиши асинхронную функцию, которая возвращает Result<(), MyError>. Используй thiserror для определения ошибок и anyhow для контекста. Покажи пример с обработкой ошибок ввода-вывода и таймаутов. Добавь логирование через tracing.
Пример:
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Timeout")]
Timeout,
}
10. Рефакторинг кода с clone() на заимствования
Задача: Убрать лишние клонирования, которые замедляют код.
Промт:
В этом коде слишком много .clone(). Предложи, как заменить их на ссылки или умные указатели. Покажи переписанный код и объясни, почему это безопасно с точки зрения borrow checker.
[вставьте код]
Пример: Замена String на &str в параметрах функций.
11. Генерация Cargo.toml с нужными фичами
Задача: Быстро настроить зависимости для асинхронного проекта.
Промт:
Создай Cargo.toml для асинхронного веб-сервера на Tokio с использованием axum, serde, tracing. Укажи версии и фичи. Добавь профиль release с оптимизациями.
Пример вывода:
[dependencies]
tokio = { version = "1.38", features = ["full"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = "0.3"
[profile.release]
lto = true
codegen-units = 1
12. Объяснение трейтов Future и Stream
Задача: Разобраться, как работают Future и Stream, и написать свою реализацию.
Промт:
Объясни разницу между Future и Stream в Rust. Напиши пример реализации простого Stream, который выдаёт числа от 1 до 5. Используй async-stream или ручную реализацию. Покажи, как его использовать в цикле while let Some(x) = stream.next().await.
Пример:
use async_stream::stream;
fn numbers() -> impl Stream<Item = i32> {
stream! {
for i in 1..=5 {
yield i;
}
}
}
Ссылка: async-stream.
Как использовать эти промты эффективно
- Всегда давайте контекст. Чем больше кода и точнее ошибка, тем полезнее ответ.
- Проверяйте сгенерированный код. Запускайте
cargo check,cargo clippy,cargo test. LLM может ошибаться в деталях. - Уточняйте. Если ответ не подходит, попросите переписать с другими ограничениями (например, без unsafe).
- Изучайте официальные источники. The Rust Programming Language, Rust Async Book, Tokio Docs.
Borrow checker и асинхронность перестают быть страшными, когда у вас есть правильные вопросы. Эти промты — ваш ускоритель. Пробуйте, адаптируйте, делитесь своими находками. И помните: компилятор — ваш друг, а AI — переводчик.
Комментарии