Практическое знакомство с Rust: 20 готовых программ с объяснением

Rust быстро набирает популярность благодаря безопасности, скорости и удобству параллелизма. Но лучший способ понять язык — написать несколько работающих программ.
В этой статье собраны минимальные, но полезные примеры, которые помогут почувствовать Rust в деле: работа с переменными, структурами, ошибками, коллекциями и файлами.
http://t.me/rust_code – в нашем телегам канале для Rust разработчиков, вы найдете множество гайдов, уроков и примеров с кодом, очень много полезного материала.
⚡ https://t.me/addlist/2Ls-snqEeytkMDgy а здесь целая папка крутых ресурсов для программистов, наслаждайтесь.
1. Программа “Hello, Rust” и базовые типы
Rust строгий и требует явности — это видно даже в простейшем коде.
fn main() {
let name = "Rust";
let version: f32 = 1.76;
println!("Hello, {name}! Current version: {version}");
}
✔ let — неизменяемая переменная
✔ println! — макрос (в Rust макросы видны по !)
2. Функции и возврат значений Rus
Rust явно описывает типы параметров и возвращаемых значений.
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(5, 7);
println!("5 + 7 = {result}");
}
✔ стрелка -> обозначает тип возвращаемого значения
✔ отсутствует return, если последняя строка выражение
3. Работа со структурами
Rust — статически типизированный язык с сильной моделью владения.
Структуры — основа прикладных программ.
struct User {
name: String,
age: u8,
}
fn main() {
let user = User {
name: "Alice".to_string(),
age: 30,
};
println!("{} is {} years old", user.name, user.age);
}
✔ строки — владение, поэтому to_string()
✔ u8 — компактный тип для маленьких чисел
4. Коллекции и итераторы
Rust продвигает функциональный стиль и безопасные итерации.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let squared: Vec<i32> = numbers.iter().map(|n| n * n).collect();
println!("Squared: {:?}", squared);
}
✔ vec![] — динамический массив
✔ iter() + map() + collect() = понятная цепочка обработки
5. Работа с файлами и обработка ошибок
Rust заставляет обрабатывать ошибки, поэтому код надёжнее.
use std::fs::File;
use std::io::{Write, Read};
fn main() -> std::io::Result<()> {
let mut file = File::create("data.txt")?;
file.write_all(b"Hello, file!")?;
let mut file = File::open("data.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("File read: {}", contents);
Ok(())
}
✔ ? — удобная распаковка ошибок (как try в других языках)
✔ Result<()> — функция может завершиться с ошибкой
Что получилось?
Мы написали:
✔ вывод текста
✔ функции
✔ структуру данных
✔ коллекции и итераторы
✔ файловый ввод/вывод с обработкой ошибок
Эти примеры отражают философию Rust:
🔹 явность типов
🔹 контроль над владением
🔹 безопасное управление памятью
🔹 строгая обработка ошибок
Что стоит попробовать дальше
Чтобы закрепить Rust:
✓ написать калькулятор
✓ мини-CLI утилиту с аргументами командной строки
✓ простой API-сервер на axum
✓ игру-консольку используя crossterm
Практическое знакомство с Rust: продвинутые примеры
Продолжаем знакомство с Rust и разбираем более интересные конструкции: собственные ошибки, трейты и дженерики, итераторы, конкурентность и асинхронный код.
Все примеры можно копировать в отдельные проекты и пробовать.
use std::fmt;
use std::fs::File;
use std::io::{self, Read};
#[derive(Debug)]enum AppError {
Io(io::Error),
EmptyFile,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "io error: {e}"),
AppError::EmptyFile => write!(f, "file is empty"),
}
}
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self {
AppError::Io(e)
}
}
fn read_non_empty(path: &str) -> Result<String, AppError> {
let mut file = File::open(path)?; // io::Error автоматически превращается в AppError
let mut buf = String::new();
file.read_to_string(&mut buf)?;
if buf.trim().is_empty() {
return Err(AppError::EmptyFile);
}
Ok(buf)
}
fn main() {
match read_non_empty("data.txt") {
Ok(text) => println!("Content: {text}"),
Err(e) => eprintln!("Error: {e}"),
}
}
2. Трейты и дженерики: общая функция для разных фигур
Трейты позволяют описывать поведение, а дженерики – писать код, который работает с разными типами.
trait Area {
fn area(&self) -> f64;
}
struct Circle {
radius: f64,
}
struct Rect {
w: f64,
h: f64,
}
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
impl Area for Rect {
fn area(&self) -> f64 {
self.w * self.h
}
}
// дженерик по трейту
fn print_area<T: Area>(shape: &T) {
println!("Area: {}", shape.area());
}
fn main() {
let c = Circle { radius: 2.0 };
let r = Rect { w: 3.0, h: 4.0 };
print_area(&c);
print_area(&r);
}
Один интерфейс Area – общая функция print_area для любых фигур, удовлетворяющих этому трейту.
3. Свой итератор
Итераторы – сильная сторона Rust. Можно сделать свой тип, который сам генерирует значения.
struct Counter {
current: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current >= self.max {
None
} else {
self.current += 1;
Some(self.current)
}
}
}
fn main() {
let sum: u32 = Counter::new(5)
.map(|n| n * 2)
.filter(|n| n % 4 == 0)
.sum();
println!("Sum: {sum}");
}
Мы определили Iterator для собственного типа и сразу встроились в экосистему методов map, filter, sum и других.
4. Конкурентность: обработка задач в нескольких потоках
Простейший пул воркеров на std::thread и канале.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel::<u32>();
// запускаем 4 воркера
for id in 0..4 {
let rx = rx.clone();
thread::spawn(move || {
for job in rx {
println!("worker {id} got job {job}");
thread::sleep(Duration::from_millis(200));
}
});
}
// отправляем задачи
for job in 1..=10 {
tx.send(job).unwrap();
}
// важный момент - закрыть исходный sender
drop(tx);
// даем воркерам время доработать
thread::sleep(Duration::from_secs(2));
}
Особенность Rust – строгая модель владения не даёт случайно гонять общие данные между потоками без синхронизации.
5. Асинхронный HTTP клиент на reqwest + tokio
Асинхронный код в Rust подойдёт для сетевых сервисов и ботов.
Cargo.toml:
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
Код:
use reqwest::Error;
#[tokio::main]
async fn main() -> Result<(), Error> {
let url = "https://httpbin.org/get";
let resp = reqwest::get(url).await?;
let status = resp.status();
let body = resp.text().await?;
println!("Status: {status}");
println!("Body: {body}");
Ok(())
}
Ключевые моменты:
#[tokio::main]запускает асинхронный рантаймasync fn mainпозволяет использоватьawait- тип
Result<(), Error>даёт удобную работу с?
6. Мини CLI: парсинг аргументов без сторонних библиотек
Если нужен простой инструмент, можно обойтись стандартной библиотекой.
use std::env;
fn main() {
let mut args = env::args().skip(1); // пропускаем имя программы
let name = args.next().unwrap_or("world".to_string());
let times: u32 = args
.next()
.unwrap_or("1".to_string())
.parse()
.unwrap_or(1);
for _ in 0..times {
println!("Hello, {name}!");
}
}
Запуск:
cargo run -- Alice 3
Вывод:
Hello, Alice!
Hello, Alice!
Hello, Alice!
Что делать дальше
Идеи для тренировки:
- переписать примеры под свою задачу: другой тип ошибок, свои структуры, свои трейты
- сделать маленький CLI инструмент, который читает файл, фильтрует строки и пишет результат
- попробовать
tokioс несколькими запросами иjoin! - сделать простую очередь задач с приоритетами и своим итератором
Rust раскрывается именно в таких небольших практических примерах: язык заставляет думать о типах, владении и ошибках, а взамен даёт производительность и надёжность на уровне системного кода.
Ещё 5 продвинутых Rust-примеров для роста сеньорского мышления
Теперь углубляемся дальше — посмотрим на темы, которые реально встречаются в продакшене:
паттерн builder, smart-pointer логика, async параллелизм, lifetime-трейты, внутреннее мутабельное состояние и borrow-контроль.
Каждый пример — минимальный и копируемый.
1) Builder-паттерн без ненужного new()
Rust позволяет строить DSL-подобные API.
# Ещё 5 продвинутых Rust-примеров для роста сеньорского мышления
Теперь углубляемся дальше — посмотрим на темы, которые реально встречаются в продакшене:
паттерн builder, smart-pointer логика, async параллелизм, lifetime-трейты, внутреннее мутабельное состояние и borrow-контроль.
Каждый пример — минимальный и копируемый.
---
## 1) Builder-паттерн без ненужного `new()`
Rust позволяет строить DSL-подобные API.
```rust
struct Config {
host: String,
port: u16,
debug: bool,
}
impl Config {
fn host(mut self, h: impl Into<String>) -> Self {
self.host = h.into();
self
}
fn port(mut self, p: u16) -> Self {
self.port = p;
self
}
fn debug(mut self) -> Self {
self.debug = true;
self
}
}
fn main() {
let cfg = Config {
host: "localhost".to_string(),
port: 0,
debug: false,
}
.host("127.0.0.1")
.port(8080)
.debug();
println!("config: {}:{} debug={}", cfg.host, cfg.port, cfg.debug);
}
✔ fluent-интерфейс без кучум методов-сетеров
✔ потребление self сохраняет неизменность из-вне
2) Внутренняя мутабельность через RefCell
Паттерн позволяет изменять данные внутри &self, сохраняя безопасный контроль за borrow-правилами.
use std::cell::RefCell;
struct Logger {
messages: RefCell<Vec<String>>,
}
impl Logger {
fn log(&self, msg: impl Into<String>) {
self.messages.borrow_mut().push(msg.into());
}
}
fn main() {
let logger = Logger {
messages: RefCell::new(Vec::new()),
};
logger.log("start");
logger.log("running");
logger.log("done");
println!("{:?}", logger.messages.borrow());
}
✔ mutation без &mut self
✔ borrow-чекающие ошибки в рантайме вместо UB
3) Асинхронная параллельность: несколько запросов и счётчик
use reqwest;
use tokio::task;
#[tokio::main]
async fn main() {
let urls = vec![
"https://httpbin.org/get",
"https://example.com",
"https://httpbin.org/uuid",
];
let tasks = urls.into_iter().map(|url| {
task::spawn(async move {
reqwest::get(url).await.unwrap().text().await.unwrap()
})
});
let results: Vec<_> = futures::future::join_all(tasks).await;
println!("Received {} responses", results.len());
}
✔ join_all — fan-out/fan-in схема параллелизма
✔ isolate workload через spawn
4) Трейты с lifetime: API, принимающие ссылки, но не владеющие
trait Printer<'a> {
fn print(&self, data: &'a str);
}
struct Console;
impl<'a> Printer<'a> for Console {
fn print(&self, data: &'a str) {
println!("-> {data}");
}
}
fn main() {
let msg = String::from("rust lifetimes example");
let printer = Console;
printer.print(&msg);
}
✔ generic lifetime на трейте
✔ объект не владеет данными, а только использует ссылку
5) Smart pointer поведение: Arc + Mutex для безопасного параллельного доступа
Rust допускает shared-state concurrency только через защищённые механизмы:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let c = Arc::clone(&counter);
let h = thread::spawn(move || {
for _ in 0..1000 {
*c.lock().unwrap() += 1;
}
});
handles.push(h);
}
for h in handles {
h.join().unwrap();
}
println!("counter = {}", *counter.lock().unwrap());
}
✔ Mutex гарантирует атомарность
✔ Arc позволяет делить владение между потоками
Что вы получили в этих 5 фрагментах
✔ Builder API
✔ interior mutability без &mut self
✔ async fan-out массовых запросов
✔ трейты с lifetime для безопасного API дизайна
✔ конкурентный доступ через Arc + Mutex
Что сделать дальше
Попробуйте:
- заменить
MutexнаRwLock - реализовать собственный trait-builder
- сделать свой iterator и iter adaptor
- переписать async пример на
tokio::sync::Semaphore - сделать DSL через макрос
Rust — язык, который раскрывается через проектирование API и управление владением.
Эти примеры — маленькие, но уже «профессионского уровня» кирпичики, из которых складываются реальные системы.
100 вопросов c собеседований на позицию middle Rust разработчика в 2025 году.



