Rust без боли: 12 промтов, которые укрощают borrow checker и ускоряют асинхронный код

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,
}

Источник: thiserror, anyhow.

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.

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

  1. Всегда давайте контекст. Чем больше кода и точнее ошибка, тем полезнее ответ.
  2. Проверяйте сгенерированный код. Запускайте cargo check, cargo clippy, cargo test. LLM может ошибаться в деталях.
  3. Уточняйте. Если ответ не подходит, попросите переписать с другими ограничениями (например, без unsafe).
  4. Изучайте официальные источники. The Rust Programming Language, Rust Async Book, Tokio Docs.

Borrow checker и асинхронность перестают быть страшными, когда у вас есть правильные вопросы. Эти промты — ваш ускоритель. Пробуйте, адаптируйте, делитесь своими находками. И помните: компилятор — ваш друг, а AI — переводчик.

← Все статьи

Комментарии

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

Подключение датчика DHT22 (температура/влажность) к AI-агенту ASI Biont: гайд по интеграции для умного дома и промышленного мониторинга

25 июля 2026

CISSP — сертифицированный специалист по информационной безопасности: освойте 8 доменов CBK с помощью AI-подготовки в 2026 году

15 июля 2026

Ettin Suite: Почему парные энкодеры и декодеры стали новым SoTA в AI

23 июня 2026

Курс AI-автоматизация бизнеса: как внедрить агентов без команды разработки на Asibiont

22 июня 2026

Курс Vibe Coding против традиционных буткемпов программирования: что выбрать в 2026 году — обзор asibiont.com на основе данных

31 июля 2026

От электронных таблиц к стратегии: как ИИ-агент ASI Biont автоматизирует рабочие процессы Oracle NetSuite ERP

7 июля 2026

15 промтов для React Native: как ускорить мобильную разработку в 2 раза

25 августа 2026

Почему обучение по ПОД/ФТ в 2026 году требует большего, чем теория: взгляд на курс Asibiont

5 июля 2026

После двух недель с Pebble Time 2 я больше никогда не куплю другие умные часы

17 июля 2026