Borrow checker, async и трейты: как заставить LLM писать идиоматичный Rust без боли — 12 промтов, проверенных на реальных проектах

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]
}
Что проверить первым После генерации кода с заимствованиями всегда запускайте `cargo check` — LLM часто предлагает решения, которые не проходят borrow checker. Если ошибка повторяется, добавьте в промт фразу «избегай изменения владения без необходимости».

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 типов.

Ключевое различие В async-коде LLM часто путает `Send` и `Sync`. Для future, которые перемещаются между потоками, нужен `Send`; для разделяемых ссылок — `Sync`. Если модель ошибается, добавьте в промт: «убедись, что future реализует 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 и покажет тест. Это помогает избежать ошибок при многопоточности.

Где чаще ошибаются LLM часто предлагает `clone()` для решения проблем с заимствованиями. Всегда проверяйте, можно ли обойтись ссылкой или изменением сигнатуры — это снижает нагрузку на аллокатор.

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

Промты из этой подборки можно комбинировать. Например, для генерации нового модуля: сначала попросите создать структуру (промт 3), затем реализовать трейт (промт 5), потом оптимизировать (промт 8). Всегда проверяйте код через cargo clippy — LLM иногда генерирует код, который компилируется, но не соответствует идиомам Rust.

Если вы только начинаете работать с Rust и LLM, начните с базовых промтов. Для углублённого изучения языка обратите внимание на курсы по программированию — они помогают систематизировать знания. А если вы пишете тесты на Rust, промты для генерации unit-тестов можно адаптировать из статьи про Python.

Что проверить первым После генерации кода запустите `cargo fmt` и `cargo clippy`. Если clippy выдаёт предупреждения, попросите LLM исправить их в следующем промте.

Эти 12 промтов покрывают основные сценарии работы с Rust: от базовых функций до асинхронности и макросов. Главное — не лениться уточнять требования к владению и временам жизни. Чем конкретнее промт, тем меньше итераций исправлений. Попробуйте применить их в своём следующем проекте — и вы увидите, как LLM перестаёт быть источником ошибок и становится надёжным ассистентом.

← Все статьи

Спросить AI по этой статье

Задайте вопрос по этой статье — ASI Biont ответит, опираясь на её текст.

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

ИИ для повседневной жизни: как построить реальный семейный бюджет с ChatGPT и Claude (без кода, 2026)

27 октября 2026

Сколько времени нужно на иностранный язык: как ИИ и кривая забывания дают разговорный английский за год при 20 минутах в день

27 октября 2026

Юнит-тесты на Python с LLM: 12 промтов от pytest-фикстур до Hypothesis, которые закрывают непокрытые ветки

27 октября 2026

Налоговый вычет в 2026: как вернуть деньги за обучение, лечение и квартиру и не потерять их на документах

27 октября 2026

Full-Stack AI Engineer: как RAG-пайплайны, векторные базы данных и развертывание LLM на самом деле работают в продакшене

28 октября 2026

Сверка актов вручную или промптом: где LLM экономит бухгалтеру часы, а где создаёт риски

12 ноября 2026

Профессия: HR-специалист от подбора до HR-стратегии — полный путь в HR: подбор, адаптация, оценка, развитие, мотивация, HR-аналитика, трудовое право и AI-инструменты в рекрутменте

28 октября 2026

Основы Computer Science: алгоритмы и структуры данных — кому подходит курс, а кому нет

11 ноября 2026

Детектор нейросетей ошибается: 7 признаков ИИ-текста, которые не заменят сервисы проверки

30 октября 2026