Rust — язык, который любят за безопасность и ненавидят за кривую обучения. LLM уже неплохо генерируют код на Python или TypeScript, но с Rust всё сложнее: модель может предложить решение, которое не компилируется из-за заимствований, или сгенерировать асинхронный код с Send-ошибками. Просто попросить «напиши на Rust» недостаточно — нужны точные промты, которые заставляют модель учитывать правила владения, времена жизни и особенности асинхронности.
В этой подборке — 12 промтов, разделённых на три уровня: базовые (для повседневных задач), продвинутые (для сложных конструкций) и экспертные (для оптимизации и рефакторинга). Каждый промт содержит задачу, готовый текст запроса и пример результата. Все примеры проверены на компиляторе Rust 1.75+.
Базовые промты: чтобы код просто собирался
1. Генерация функции с явным указанием владения
Задача: написать функцию, которая принимает строку и возвращает её длину, не забирая владение.
Промт:
Напиши на Rust функцию `string_length`, которая принимает `&str` и возвращает `usize`. Не используй `String` во входных параметрах. Добавь doc-комментарий с примером использования. Убедись, что функция не вызывает `clone()`.
Пример результата:
/// Возвращает длину строки в байтах.
///
/// # Пример
/// ```
/// assert_eq!(string_length("hello"), 5);
/// ```
fn string_length(s: &str) -> usize {
s.len()
}
Модель правильно выбирает &str вместо String и не пытается клонировать данные. Это базовый, но важный шаг: многие LLM по умолчанию предлагают String, что приводит к лишним аллокациям.
2. Исправление ошибки заимствования
Задача: модель сгенерировала код с ошибкой cannot borrow as mutable, нужно её исправить.
Промт:
В этом коде ошибка компиляции: `cannot borrow `vec` as mutable because it is also borrowed as immutable`. Объясни причину и предложи минимальное исправление без изменения сигнатуры функции. Код:
fn process(v: &Vec<i32>) -> i32 {
let first = &v[0];
v.push(42);
*first
}
Пример результата: модель объясняет, что first держит неизменяемую ссылку, а push требует изменяемую, и предлагает либо клонировать значение, либо изменить порядок операций:
fn process(v: &mut Vec<i32>) -> i32 {
v.push(42);
v[0]
}
3. Создание структуры с методами
Задача: сгенерировать структуру User с полями и методами, соблюдая идиомы Rust.
Промт:
Создай структуру `User` с полями `name: String`, `age: u8`, `email: String`. Добавь конструктор `new`, метод `greet(&self) -> String` и метод `set_age(&mut self, age: u8)`. Используй `impl` блок. Не делай поля публичными без необходимости. Напиши unit-тест для `greet`.
Пример результата: модель генерирует структуру с приватными полями, конструктором и тестом. Важно: LLM может сделать поля pub, если не указать явно — это нарушает инкапсуляцию.
Продвинутые промты: времена жизни, трейты и async
4. Функция с временем жизни
Задача: написать функцию, возвращающую ссылку на более длинную из двух строк.
Промт:
Напиши функцию `longest`, которая принимает две строковые срезы `&str` и возвращает `&str` — более длинный из них. Укажи времена жизни явно. Объясни, почему без аннотации `'a` компилятор выдаст ошибку. Приведи пример использования.
Пример результата:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Модель добавляет аннотацию и поясняет, что возвращаемая ссылка живёт не дольше наименьшего из входных. Без этого промта LLM часто забывает про времена жизни и генерирует код, который не компилируется.
5. Реализация трейта для структуры
Задача: реализовать трейт Display для структуры Point.
Промт:
Реализуй трейт `std::fmt::Display` для структуры `Point { x: f64, y: f64 }`. Формат вывода: `(x, y)`. Используй `write!`. Не используй `Debug`. Добавь пример, как вывести точку через `println!`.
Пример результата:
use std::fmt;
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
6. Асинхронная функция с tokio
Задача: написать async-функцию, которая делает HTTP-запрос и парсит JSON.
Промт:
Напиши асинхронную функцию `fetch_user` с использованием `reqwest` и `tokio`, которая принимает `user_id: u64`, делает GET-запрос к `https://api.example.com/users/{id}` и возвращает `Result<User, Box<dyn std::error::Error>>`. Определи структуру `User` с полями `id` и `name`. Убедись, что future является `Send`. Добавь `#[tokio::main]` для примера вызова.
Пример результата: модель генерирует код с async fn, использует reqwest::get, десериализует через serde_json. Важно: LLM может забыть про Send, если не указать явно — проверьте, что returned future не содержит Rc или других не-Send типов.
7. Обработка ошибок через Result и ?
Задача: написать функцию, которая читает файл и парсит число, используя ?.
Промт:
Напиши функцию `read_number_from_file`, которая принимает путь к файлу, читает его содержимое, парсит в `i32` и возвращает `Result<i32, Box<dyn std::error::Error>>`. Используй оператор `?` для обработки ошибок. Не используй `unwrap()`. Добавь обработку ошибок парсинга с помощью `map_err`.
Пример результата:
use std::fs;
use std::num::ParseIntError;
fn read_number_from_file(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let num = content.trim().parse::<i32>()?;
Ok(num)
}
Экспертные промты: оптимизация, рефакторинг и макросы
8. Устранение лишних клонирований
Задача: оптимизировать код, убрав clone() там, где можно обойтись заимствованием.
Промт:
Дан код:
fn process(data: &Vec<String>) -> Vec<String> {
let mut result = Vec::new();
for s in data {
result.push(s.clone());
}
result
}
Предложи оптимизацию, которая избегает клонирования строк. Можно менять сигнатуру функции. Объясни, как использовать срезы и времена жизни.
Пример результата: модель предлагает вернуть Vec<&str> или использовать iter().cloned(), но лучше — изменить сигнатуру на fn process(data: &[String]) -> Vec<&str>. Это уменьшает аллокации.
9. Макрос для генерации структур
Задача: написать декларативный макрос, который создаёт структуру с полями и методом new.
Промт:
Напиши макрос `create_struct!`, который принимает имя структуры и список полей с типами, и генерирует структуру с публичными полями и методом `new`, принимающим все поля. Используй `macro_rules!`. Приведи пример использования для структуры `Person { name: String, age: u8 }`.
Пример результата: модель генерирует макрос с повторением $( $field:ident : $ty:ty ),*. Это сложная задача, LLM может ошибиться в синтаксисе — проверьте компиляцию.
10. Рефакторинг с impl Trait
Задача: заменить возврат Box<dyn Trait> на impl Trait, если это возможно.
Промт:
В функции `fn make_adder(x: i32) -> Box<dyn Fn(i32) -> i32>` замени возвращаемый тип на `impl Fn(i32) -> i32`. Объясни, в каких случаях такая замена невозможна. Приведи пример вызова.
Пример результата: модель предлагает `fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move
|y| x + y }. Важно:impl Trait` не работает, если нужно вернуть разные типы в разных ветках.
11. Асинхронный трейт (Rust 1.75+)
Задача: реализовать async-метод в трейте.
Промт:
Напиши трейт `AsyncHandler` с асинхронным методом `handle(&self) -> Result<(), Box<dyn std::error::Error>>`. Реализуй его для структуры `MyHandler`. Используй `async fn` в трейте (стабильно с Rust 1.75). Добавь пример использования с `tokio::main`.
Пример результата: модель генерирует трейт с async fn и реализацию. Напомните, что для dyn-совместимости нужен крейт async-trait.
12. Проверка на Send и Sync
Задача: убедиться, что структура может передаваться между потоками.
Промт:
Дана структура `struct Data { value: Rc<i32> }`. Объясни, почему она не реализует `Send`. Предложи изменение, чтобы она стала `Send`, и напиши тест, который проверяет это с помощью `static_assertions::assert_impl_all!`.
Пример результата: модель заменит Rc на Arc и покажет тест. Это помогает избежать ошибок при многопоточности.
Как использовать промты в реальной работе
Промты из этой подборки можно комбинировать. Например, для генерации нового модуля: сначала попросите создать структуру (промт 3), затем реализовать трейт (промт 5), потом оптимизировать (промт 8). Всегда проверяйте код через cargo clippy — LLM иногда генерирует код, который компилируется, но не соответствует идиомам Rust.
Если вы только начинаете работать с Rust и LLM, начните с базовых промтов. Для углублённого изучения языка обратите внимание на курсы по программированию — они помогают систематизировать знания. А если вы пишете тесты на Rust, промты для генерации unit-тестов можно адаптировать из статьи про Python.
Эти 12 промтов покрывают основные сценарии работы с Rust: от базовых функций до асинхронности и макросов. Главное — не лениться уточнять требования к владению и временам жизни. Чем конкретнее промт, тем меньше итераций исправлений. Попробуйте применить их в своём следующем проекте — и вы увидите, как LLM перестаёт быть источником ошибок и становится надёжным ассистентом.