Промты для Rust: как перестать бороться с borrow checker и писать production-код за вечер
Borrow checker — это одновременно благословение и проклятие Rust. Он гарантирует безопасность памяти без сборщика мусора, но поначалу кажется, что компилятор просто издевается. Знакомо? Вы пишете let mut x = vec![1,2,3]; let y = &x[0]; x.push(4); и получаете ошибку, которая рушит все планы. По данным опроса Stack Overflow Developer Survey 2025, Rust остаётся самым любимым языком программирования, но 67% разработчиков называют кривую обучения одной из главных сложностей. И вот здесь на помощь приходят правильно составленные промты для AI-ассистентов.
Эта статья — не просто список команд. Это набор проверенных шаблонов, которые помогают объяснить компилятору (и себе) логику владения, заимствования и времени жизни. Мы разберём три уровня: базовые промты для быстрого исправления ошибок, продвинутые — для рефакторинга и оптимизации, и экспертные — для асинхронного Rust и production-кода с Tokio. Каждый промт сопровождается реальным примером, который можно скопировать и вставить в ваш любимый AI-инструмент (ChatGPT, Claude, GitHub Copilot Chat).
Важно: промты не заменяют понимание, но они ускоряют обучение и помогают избежать типичных ловушек. Используйте их как ускоритель, а не как костыль.
Базовые промты: гасим пожары borrow checker
1. Объяснение ошибки E0502 (конфликт заимствований)
Задача: Вы пытаетесь изменять вектор, пока держите ссылку на его элемент, и получаете ошибку cannot borrow as mutable because it is also borrowed as immutable.
Промт:
Я новичок в Rust и получаю ошибку компиляции. Вот код:
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{}", first);
}
Объясни, почему это не компилируется, и как исправить, сохранив логику. Дай два варианта: с клонированием и без.
Пример результата:
AI объяснит, что first — это неизменяемая ссылка, а push требует изменяемой, и они не могут сосуществовать. Первый вариант: let first = v[0]; (копирование, так как i32 — Copy). Второй: использовать v.first().copied() или изменить порядок операций. Также покажет, как применить split_at_mut для более сложных случаев.
2. Промт для преобразования кода с C++ на Rust
Задача: Перевести функцию, работающую с указателями, в идиоматичный Rust без unsafe.
Промт:
Переведи следующий C++ код на Rust, используя безопасные абстракции (без unsafe, если возможно). Объясни, как ownership и borrowing заменяют ручное управление памятью.
// C++ код
void process(int* arr, int size) {
for (int i = 0; i < size; ++i) {
arr[i] *= 2;
}
}
Пример результата:
AI предложит:
fn process(arr: &mut [i32]) {
for x in arr.iter_mut() {
*x *= 2;
}
}
И пояснит, что срез &mut [i32] гарантирует эксклюзивный доступ, а итератор iter_mut безопасно предоставляет изменяемые ссылки на элементы.
3. Промт для работы с временами жизни (lifetimes)
Задача: Функция возвращает ссылку на строку, но компилятор требует аннотацию времени жизни.
Промт:
У меня есть функция:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
Компилятор просит указать времена жизни. Объясни, почему это необходимо, и напиши правильную сигнатуру. Приведи пример использования.
Пример результата:
AI объяснит, что возвращаемая ссылка может указывать на x или y, поэтому нужно связать время жизни результата с обоими аргументами:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
Покажет, что это гарантирует, что результат не переживёт свои входные данные.
4. Промт для замены Rc<RefCell<T>> на более идиоматичный код
Задача: Избавиться от избыточного использования Rc<RefCell<T>> там, где можно обойтись обычными ссылками.
Промт:
Я использую Rc<RefCell<Vec<String>>> для общего доступа к списку. Но это кажется избыточным. Проанализируй код и предложи альтернативы с обычными ссылками или Arc<Mutex<>>, если нужна потокобезопасность.
struct Shared {
data: Rc<RefCell<Vec<String>>>,
}
impl Shared {
fn add(&self, item: String) {
self.data.borrow_mut().push(item);
}
}
Пример результата:
AI предложит, если потоков нет, использовать &mut self и обычный Vec<String>. Если есть — Arc<Mutex<Vec<String>>> с блокировкой. Покажет, как уменьшить количество borrow_mut() и избежать паник во время выполнения.
Продвинутые промты: рефакторинг и оптимизация
5. Промт для переписывания итеративного алгоритма на функциональный стиль
Задача: Убрать ручные циклы и индексы, сделав код более декларативным.
Промт:
Перепиши этот код на идиоматичный Rust с использованием итераторов и комбинаторов. Объясни преимущества по производительности и читаемости.
fn sum_even_squares(nums: &[i32]) -> i32 {
let mut sum = 0;
for &n in nums {
if n % 2 == 0 {
sum += n * n;
}
}
sum
}
Пример результата:
fn sum_even_squares(nums: &[i32]) -> i32 {
nums.iter()
.filter(
|&&n| n % 2 == 0)
.map(
|&n| n * n)
.sum()
}
AI пояснит, что итераторы часто оптимизируются лучше ручных циклов благодаря устранению проверок границ.
6. Промт для обработки ошибок с Result и ?
Задача: Заменить unwrap() на корректную обработку ошибок.
Промт:
У меня есть функция, которая читает файл и парсит JSON. Сейчас везде unwrap. Перепиши с использованием Result и оператора ?, добавь свой тип ошибки через thiserror. Приведи пример main с обработкой.
use std::fs;
fn read_config(path: &str) -> String {
let content = fs::read_to_string(path).unwrap();
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
json["key"].as_str().unwrap().to_string()
}
Пример результата:
AI создаст enum ошибок, реализует From для io::Error и serde_json::Error, использует ? и возвращает Result<String, ConfigError>. Покажет, как в main вывести ошибку и завершиться с кодом 1.
7. Промт для оптимизации производительности с Cow
Задача: Избежать лишних аллокаций при работе со строками.
Промт:
Функция принимает &str, но иногда возвращает изменённую строку, иногда исходную. Сейчас всегда создаётся String. Как использовать Cow<str> для оптимизации? Напиши пример с бенчмарком.
Пример результата:
AI покажет:
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}
И объяснит, что при отсутствии пробелов аллокации не будет.
Экспертные промты: асинхронность, Tokio и production
8. Промт для написания асинхронного сервера на Tokio
Задача: Быстро создать TCP-эхо-сервер с обработкой ошибок и логированием.
Промт:
Напиши асинхронный эхо-сервер на Tokio 1.x, который принимает подключения, читает строки и отправляет их обратно в верхнем регистре. Используй tokio::net::TcpListener, tokio::io::{AsyncBufReadExt, AsyncWriteExt}, добавь tracing для логов. Обработай ошибки без паник.
Пример результата:
AI сгенерирует полный код с #[tokio::main], циклом accept, спавном задач через tokio::spawn, и обработкой ошибок через match. Покажет зависимости в Cargo.toml.
9. Промт для использования tokio::select! и отмены задач
Задача: Корректно завершить долгую операцию по таймауту.
Промт:
У меня есть асинхронная функция fetch_data, которая может зависнуть. Нужно прервать её через 5 секунд с помощью tokio::time::timeout или select!. Объясни разницу и приведи пример с обработкой ошибки.
Пример результата:
AI покажет:
use tokio::time::{timeout, Duration};
let result = timeout(Duration::from_secs(5), fetch_data()).await;
match result {
Ok(Ok(data)) => println!("Получено: {:?}", data),
Ok(Err(e)) => eprintln!("Ошибка: {}", e),
Err(_) => eprintln!("Таймаут"),
}
И пояснит, что select! полезен для конкуренции нескольких футур.
10. Промт для написания тестов с tokio::test и моками
Задача: Протестировать асинхронную функцию без реальной сети.
Промт:
Напиши асинхронный тест для функции, которая делает HTTP-запрос. Используй tokio::test и mockall для мока трейта HttpClient. Покажи структуру трейта, мока и теста.
Пример результата:
AI создаст трейт HttpClient с методом async fn get(&self, url: &str) -> Result<String>, сгенерирует мок через mockall::automock, напишет тест с #[tokio::test] и проверкой.
11. Промт для профилирования и устранения узких мест
Задача: Ускорить существующий код, найти аллокации.
Промт:
Проанализируй этот фрагмент на предмет лишних аллокаций и блокировок. Предложи оптимизации с использованием Cow, SmallVec или изменения структуры данных. Приведи пример до/после.
fn process_items(items: &[String]) -> Vec<String> {
items.iter().map(
|s| s.to_uppercase()).collect()
}
Пример результата:
AI предложит использовать Vec<Cow<str>> или изменить сигнатуру на impl Iterator<Item=Cow<str>>, чтобы избежать промежуточного Vec.
12. Промт для написания production-ready CLI с clap и anyhow
Задача: Создать утилиту с аргументами, цветным выводом и обработкой ошибок.
Промт:
Напиши CLI на Rust с clap 4.x, который принимает путь к файлу и флаг --verbose. Используй anyhow для ошибок, colored для вывода. Добавь подкоманды. Приведи полный main.rs и Cargo.toml.
Пример результата:
AI сгенерирует структуру с #[derive(Parser)], обработкой std::process::exit, и примером вывода.
Как использовать эти промты эффективно
Главное правило: всегда давайте AI контекст. Вставляйте полный текст ошибки компилятора, версию Rust (например, 1.80), используемые крейты. Чем конкретнее вопрос, тем полезнее ответ. Не бойтесь просить объяснить «почему», а не только «как исправить» — это ускоряет обучение.
Помните, что borrow checker — не враг, а инструмент. Со временем вы начнёте предугадывать его требования. А промты пусть станут вашим ускорителем на этом пути.
Экспериментируйте, комбинируйте, адаптируйте под свой проект. И пусть ваш код компилируется с первого раза!
Комментарии