Практическое знакомство с 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 году.
      +1
      1
      +1
      1
      +1
      0
      +1
      0
      +1
      0

      Ответить

      Ваш адрес email не будет опубликован. Обязательные поля помечены *