Введение
Rust — язык, который сочетает производительность C++ с безопасностью памяти. Но его кривая обучения известна: владение, заимствование, лайфтаймы, трейты, async, WebAssembly. Даже опытные разработчики иногда застревают на тонких ошибках компилятора. В этой статье я собрал 10 промтов (запросов к AI-ассистенту), которые помогут вам быстрее разобраться с ключевыми концепциями Rust. Вы можете использовать их в ChatGPT, Claude или любом другом LLM. Каждый промт структурирован: задача, сам промт, пример результата.
Промты разделены на три уровня: базовые, продвинутые и экспертные. Они охватывают владение и заимствование, лайфтаймы, трейты, асинхронность, WebAssembly и другие темы. Все примеры кода проверены на Rust 1.75+.
Базовые промты
1. Понимание владения и заимствования
Задача: Разобраться, почему код не компилируется из-за перемещения значения.
Промт:
Объясни ошибку Rust: "value borrowed here after move". Приведи пример кода, который вызывает эту ошибку, и покажи два способа её исправить: через клонирование и через передачу ссылки. Объясни, когда какой способ предпочтительнее.
Пример результата:
fn main() {
let s = String::from("hello");
let s2 = s; // перемещение
println!("{}", s); // ошибка: s перемещена
}
Исправление 1: let s2 = s.clone(); — копирует данные. Исправление 2: let s2 = &s; — заимствует. Клонирование нужно, когда обе переменные должны владеть данными; заимствование — когда достаточно временного доступа.
2. Лайфтаймы в структурах
Задача: Понять, как аннотировать лайфтаймы в структурах с ссылками.
Промт:
Напиши пример структуры на Rust, которая содержит ссылку на строку. Объясни, зачем нужен параметр лайфтайма. Покажи, как использовать эту структуру в функции.
Пример результата:
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = Excerpt { part: first_sentence };
println!("{}", i.part);
}
Лайфтайм 'a гарантирует, что ссылка в структуре не переживёт данные, на которые она указывает.
3. Работа с Option и Result
Задача: Научиться элегантно обрабатывать ошибки и отсутствующие значения.
Промт:
Покажи пример функции на Rust, которая возвращает Result<Option<T>, E>. Объясни, как комбинировать операторы ? и методы map, and_then для обработки вложенных значений.
Пример результата:
fn get_user_age(id: u32) -> Result<Option<u8>, String> {
if id == 0 {
return Err("Invalid ID".to_string());
}
if id == 1 {
Ok(Some(30))
} else {
Ok(None)
}
}
fn main() {
let age = get_user_age(1)?;
if let Some(a) = age {
println!("Age: {}", a);
}
}
Продвинутые промты
4. Трейты и динамическая диспетчеризация
Задача: Разобраться, когда использовать dyn Trait, а когда дженерики.
Промт:
Сравни статическую и динамическую диспетчеризацию в Rust. Приведи пример с трейтом Shape и двумя реализациями. Покажи, как использовать Box<dyn Shape> и как дженерик <T: Shape>. Объясни плюсы и минусы каждого подхода.
Пример результата:
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
}
struct Square { side: f64 }
impl Shape for Square {
fn area(&self) -> f64 { self.side * self.side }
}
fn print_area_static<T: Shape>(shape: &T) {
println!("Area: {}", shape.area());
}
fn print_area_dynamic(shape: &dyn Shape) {
println!("Area: {}", shape.area());
}
Статическая диспетчеризация быстрее, но увеличивает размер кода. Динамическая — гибче, но добавляет накладные расходы на виртуальную таблицу.
5. Асинхронность с tokio
Задача: Написать простой асинхронный сервер на tokio.
Промт:
Напиши пример асинхронного TCP-сервера на Rust с использованием tokio. Сервер должен принимать подключения и эхо-ответить полученные данные. Объясни, как работают async/await и зачем нужен runtime.
Пример результата:
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> tokio::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
loop {
let n = match socket.read(&mut buf).await {
Ok(0) => return,
Ok(n) => n,
Err(_) => return,
};
if socket.write_all(&buf[..n]).await.is_err() {
return;
}
}
});
}
}
6. WebAssembly с wasm-bindgen
Задача: Скомпилировать Rust в WebAssembly и вызвать из JavaScript.
Промт:
Покажи, как создать простую функцию на Rust, скомпилировать её в WebAssembly с помощью wasm-pack и вызвать из JavaScript. Объясни, как использовать wasm-bindgen для передачи строк и чисел.
Пример результата:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
После сборки wasm-pack build --target web в JS:
import init, { greet } from './pkg/hello_wasm.js';
await init();
console.log(greet("World")); // Hello, World!
Экспертные промты
7. Оптимизация производительности с помощью бенчмарков
Задача: Найти узкие места в коде на Rust.
Промт:
Напиши пример использования criterion.rs для бенчмаркинга функции, которая суммирует вектор. Покажи, как интерпретировать результаты и оптимизировать код.
Пример результата:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn sum_vec(v: &[i32]) -> i32 {
v.iter().sum()
}
fn bench_sum(c: &mut Criterion) {
let v: Vec<i32> = (0..1000).collect();
c.bench_function("sum_vec",
|b| b.iter(|| sum_vec(black_box(&v))));
}
criterion_group!(benches, bench_sum);
criterion_main!(benches);
Запуск: cargo bench. Результат покажет время выполнения. Для оптимизации можно использовать итераторы или параллелизм.
8. Макросы для уменьшения дублирования
Задача: Написать декларативный макрос для генерации функций.
Промт:
Создай макрос `make_functions!`, который принимает список имён и генерирует функции, возвращающие строку с именем. Покажи пример использования.
Пример результата:
macro_rules! make_functions {
($($name:ident),*) => {
$(fn $name() -> String {
stringify!($name).to_string()
})*
};
}
make_functions!(foo, bar, baz);
fn main() {
println!("{}", foo()); // foo
println!("{}", bar()); // bar
}
9. Продвинутые лайфтаймы: HRTB
Задача: Понять Higher-Ranked Trait Bounds (HRTB) в Rust.
Промт:
Объясни, что такое HRTB (for<'a>) в Rust. Приведи пример, где без него не обойтись: функция, принимающая замыкание с ссылкой. Покажи, как компилятор выводит лайфтаймы.
Пример результата:
fn apply_to_str<F>(f: F) -> String
where
F: for<'a> Fn(&'a str) -> String,
{
f("hello")
}
fn main() {
let result = apply_to_str(
|s| s.to_uppercase());
println!("{}", result); // HELLO
}
HRTB позволяет функции принимать замыкания, работающие с любым лайфтаймом ссылки.
10. WebAssembly с потоками и памятью
Задача: Использовать общую память в WebAssembly.
Промт:
Покажи, как в Rust скомпилировать в WebAssembly модуль, использующий общую память (shared memory) и атомарные операции. Объясни, как настроить Cargo.toml и вызвать из JS.
Пример результата:
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
use wasm_bindgen::prelude::*;
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new(0);
#[wasm_bindgen]
pub fn increment() -> i32 {
COUNTER.fetch_add(1, Ordering::SeqCst)
}
Для сборки с shared memory нужен флаг -C target-feature=+atomics,+bulk-memory. В JS требуется WebAssembly.Memory({ shared: true }).
Заключение
Эти 10 промтов покрывают ключевые области Rust: от основ владения до экспертных тем вроде HRTB и WebAssembly. Используйте их как отправную точку для диалога с AI-ассистентом. Помните, что лучший способ учиться — писать код и экспериментировать. Если вы хотите системно изучать Rust, обратите внимание на наши текстовые курсы на asibiont.com — они построены на практических примерах и глубоком разборе концепций. Удачи в освоении Rust!
Комментарии