Rust
Rust — це сучасна компільована мова програмування, орієнтована на безпеку пам’яті, високу продуктивність, надійну конкурентність і системне програмування.
Rust часто використовують там, де раніше традиційно застосовували C або C++: системні утиліти, серверні застосунки, embedded, WebAssembly, CLI-інструменти, мережеві сервіси, інфраструктурне програмне забезпечення, криптографія, блокчейн, ігрові рушії та високопродуктивні бібліотеки.
Основна ідея: Rust намагається поєднати продуктивність C/C++ із сильнішими гарантіями безпеки пам’яті на етапі компіляції.
Загальний опис
Rust створений для розробки швидкого й надійного програмного забезпечення без garbage collector. Основна відмінність Rust — система ownership і borrowing, яка дозволяє компілятору перевіряти правильність роботи з пам’яттю ще до запуску програми.
Rust допомагає уникати багатьох типових помилок:
- use-after-free;
- double free;
- dangling pointers;
- data races;
- частини memory leaks;
- небезпечного спільного доступу до mutable data;
- багатьох помилок concurrency;
- неконтрольованого доступу до пам’яті.
Перевага: Rust змушує програміста явно описувати ownership, borrowing і mutability, тому багато помилок знаходяться ще під час компіляції.
Для чого використовується Rust
Rust використовується у різних напрямах розробки.
Типові сценарії:
- системне програмування;
- CLI-інструменти;
- серверні застосунки;
- high-performance backend;
- embedded systems;
- WebAssembly;
- мережеві сервіси;
- криптографічні бібліотеки;
- блокчейн;
- game development;
- операційні системи;
- драйвери;
- бази даних;
- компілятори;
- інфраструктурне ПЗ;
- security-sensitive software.
Важливо: Rust не є “простішою заміною C”. Він має іншу модель мислення, особливо через ownership, borrowing, lifetimes і строгий компілятор.
Перша програма на Rust
Класичний приклад програми на Rust:
fn main() {
println!("Hello, world!");
}
У цьому прикладі:
- `fn main()` — точка входу в програму;
- `println!` — макрос для виведення тексту;
- `!` означає, що викликається макрос, а не звичайна функція.
Суть прикладу: Rust-програма починається з функції `main`, а виведення тексту часто виконується через макрос `println!`.
rustc
rustc — це компілятор Rust.
Він перетворює Rust-код на виконуваний файл або бібліотеку.
Приклад компіляції одного файлу:
rustc main.rs
./main
У реальних проєктах частіше використовують Cargo, а не прямий виклик `rustc`.
Практична роль: rustc не лише компілює код, а й виконує суворі перевірки ownership, borrowing, lifetimes і типів.
Cargo
Cargo — це офіційний build system і package manager для Rust.
Cargo використовується для:
- створення проєктів;
- збірки;
- запуску;
- тестування;
- керування залежностями;
- публікації crates;
- форматування workflow;
- запуску прикладів;
- документації.
Створення нового проєкту:
cargo new hello_rust
cd hello_rust
cargo run
Типові команди:
cargo build
cargo run
cargo test
cargo check
cargo doc
cargo fmt
cargo clippy
Перевага Cargo: Rust має сильну стандартну культуру збірки, тестування й залежностей, тому старт проєкту зазвичай простіший, ніж у C/C++.
Crates
Crate — це одиниця компіляції й пакування в Rust.
Crate може бути:
- binary crate — виконувана програма;
- library crate — бібліотека;
- internal crate — частина workspace;
- published crate — пакет на crates.io.
Залежності описуються у файлі `Cargo.toml`.
Приклад:
[dependencies]
serde = "1"
tokio = { version = "1", features = ["full"] }
Практична роль: crates дозволяють повторно використовувати бібліотеки й будувати модульні Rust-проєкти.
Cargo.toml
Cargo.toml — це конфігураційний файл Rust-проєкту.
Він містить:
- назву пакета;
- версію;
- edition;
- dependencies;
- dev-dependencies;
- build settings;
- features;
- metadata.
Приклад:
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
Порада: у Rust-проєкті важливо контролювати залежності, features і версії crates, особливо для production.
Ownership
Ownership — це центральна концепція Rust.
Кожне значення в Rust має власника. Коли власник виходить з області видимості, значення автоматично звільняється.
Приклад:
fn main() {
let text = String::from("hello");
println!("{}", text);
}
Коли `text` виходить з області видимості, пам’ять для `String` звільняється автоматично.
Суть ownership: Rust знає, хто відповідає за значення, і автоматично звільняє ресурс, коли власник більше не потрібен.
Move semantics
У Rust значення може бути переміщене від одного власника до іншого.
Приклад:
fn main() {
let a = String::from("hello");
let b = a;
println!("{}", b);
// println!("{}", a); // помилка: a більше не є власником
}
Після `let b = a;` власником рядка стає `b`, а `a` більше не можна використовувати.
Важливо: move semantics — одна з причин, чому Rust може гарантувати безпеку пам’яті без garbage collector.
Borrowing
Borrowing дозволяє тимчасово передати доступ до значення без передачі ownership.
Приклад:
fn print_text(text: &String) {
println!("{}", text);
}
fn main() {
let message = String::from("hello");
print_text(&message);
println!("{}", message);
}
`&message` — це immutable reference.
Суть borrowing: значення можна “позичити” функції, не передаючи їй власність.
Mutable borrowing
Щоб змінити значення через reference, потрібне mutable borrowing.
Приклад:
fn add_exclamation(text: &mut String) {
text.push('!');
}
fn main() {
let mut message = String::from("hello");
add_exclamation(&mut message);
println!("{}", message);
}
Правило: у Rust може бути багато immutable references або одна mutable reference, але не обидва варіанти одночасно.
Borrow checker
Borrow checker — це частина компілятора Rust, яка перевіряє правила borrowing.
Він контролює:
- хто володіє значенням;
- чи можна використовувати reference;
- чи не живе reference довше за значення;
- чи немає одночасного mutable і immutable доступу;
- чи немає data race в safe Rust.
Практична роль: borrow checker часто здається суворим, але саме він знаходить багато помилок до запуску програми.
Lifetimes
Lifetimes — це спосіб Rust описувати, як довго references залишаються дійсними.
У простих випадках компілятор виводить lifetimes сам.
Приклад із явним lifetime:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() {
a
} else {
b
}
}
Важливо: lifetimes не подовжують життя об’єктів. Вони лише описують компілятору, як довго reference може бути безпечним.
Memory safety
Memory safety — одна з головних цілей Rust.
Safe Rust запобігає багатьом помилкам:
- dangling references;
- use-after-free;
- double free;
- data races;
- некоректному shared mutable state;
- частині invalid memory access.
Головна перевага: Rust дає memory safety без garbage collector, використовуючи ownership, borrowing і перевірки компілятора.
Safe Rust і Unsafe Rust
Rust має два режими:
- Safe Rust — звичайний код із гарантіями компілятора;
- Unsafe Rust — код, де дозволені низькорівневі операції, які компілятор не може повністю перевірити.
Unsafe потрібен для:
- роботи з raw pointers;
- FFI;
- системних API;
- низькорівневих оптимізацій;
- embedded;
- взаємодії з C;
- реалізації деяких структур даних.
Приклад:
unsafe {
// низькорівневі операції
}
Критично: `unsafe` не вимикає всі перевірки Rust. Але воно покладає частину відповідальності за безпеку на програміста.
Змінні
У Rust змінні за замовчуванням immutable.
Приклад:
let count = 10;
// count = 11; // помилка
Щоб змінна була змінною, потрібно явно написати `mut`:
let mut count = 10;
count = 11;
Суть mutability: Rust заохочує незмінність за замовчуванням, а змінність потрібно вказувати явно.
Типи даних
Rust має статичну типізацію.
Основні типи:
- `i8`, `i16`, `i32`, `i64`, `i128`, `isize`;
- `u8`, `u16`, `u32`, `u64`, `u128`, `usize`;
- `f32`, `f64`;
- `bool`;
- `char`;
- `String`;
- `&str`;
- tuples;
- arrays;
- slices;
- structs;
- enums.
Приклад:
let age: u32 = 25;
let price: f64 = 19.99;
let active: bool = true;
let letter: char = 'A';
Практична роль: типи допомагають Rust знаходити помилки ще під час компіляції.
Функції
Функції в Rust оголошуються через `fn`.
Приклад:
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(2, 3);
println!("{}", result);
}
У Rust останній вираз без крапки з комою може бути значенням, що повертається.
Суть функції: функція в Rust має чіткі типи аргументів і тип результату.
Struct
struct дозволяє створювати власні типи даних.
Приклад:
struct User {
id: u32,
name: String,
}
fn main() {
let user = User {
id: 1,
name: String::from("Alice"),
};
println!("{} {}", user.id, user.name);
}
Практична роль: struct використовується для опису об’єктів, конфігурацій, даних API, доменних моделей і стану програми.
Impl
impl використовується для реалізації методів для типу.
Приклад:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle {
width: 10,
height: 5,
};
println!("{}", rect.area());
}
Суть impl: `impl` дозволяє додавати поведінку до типів без класичної об’єктної моделі з inheritance.
Enum
enum у Rust набагато потужніший, ніж enum у багатьох інших мовах.
Приклад:
enum Status {
New,
Active,
Blocked,
}
fn main() {
let status = Status::Active;
}
Enum може також містити дані:
enum Message {
Quit,
Text(String),
Move { x: i32, y: i32 },
}
Перевага enum: Rust enum дозволяє описувати різні варіанти стану без небезпечних null або неявних структур.
Pattern matching
Pattern matching дозволяє зручно обробляти enum, Option, Result та інші структури.
Приклад:
enum Status {
New,
Active,
Blocked,
}
fn print_status(status: Status) {
match status {
Status::New => println!("New"),
Status::Active => println!("Active"),
Status::Blocked => println!("Blocked"),
}
}
Суть match: Rust змушує явно обробляти всі важливі варіанти, що зменшує кількість пропущених випадків.
Option
Option використовується, коли значення може бути або не бути.
Rust не має звичайного `null` для більшості safe-сценаріїв.
Приклад:
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("User: {}", name),
None => println!("User not found"),
}
}
Важливо: Option змушує програміста явно обробити випадок відсутності значення.
Result
Result використовується для обробки помилок.
Приклад:
use std::fs;
fn main() {
let content = fs::read_to_string("data.txt");
match content {
Ok(text) => println!("{}", text),
Err(error) => println!("Error: {}", error),
}
}
`Result` має два варіанти:
- `Ok(value)` — успішний результат;
- `Err(error)` — помилка.
Суть Result: помилки є частиною типу, тому їх важче випадково проігнорувати.
Оператор ?
Оператор `?` спрощує передачу помилки вище.
Приклад:
use std::fs;
use std::io;
fn read_file(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?;
Ok(content)
}
Якщо виникає помилка, `?` повертає її з функції.
Практична користь: `?` дозволяє писати чистий код обробки помилок без довгих вкладених `match`.
Traits
Trait описує поведінку, яку може реалізувати тип.
Приклад:
trait Printable {
fn print(&self);
}
struct User {
name: String,
}
impl Printable for User {
fn print(&self) {
println!("{}", self.name);
}
}
Traits використовуються для:
- абстракцій;
- polymorphism;
- generic programming;
- спільних інтерфейсів;
- dependency boundaries;
- тестування;
- бібліотек.
Суть trait: trait описує, що тип уміє робити, не прив’язуючись до конкретної реалізації.
Generics
Generics дозволяють писати код, який працює з різними типами.
Приклад:
fn first<T>(items: &[T]) -> Option<&T> {
items.first()
}
Generics часто використовуються разом із traits:
fn print_item<T: std::fmt::Display>(item: T) {
println!("{}", item);
}
Практична роль: generics дозволяють створювати гнучкі бібліотеки без втрати типобезпеки.
Modules
Modules допомагають організувати код.
Приклад:
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let result = math::add(2, 3);
println!("{}", result);
}
`pub` робить елемент доступним ззовні модуля.
Практична роль: modules дозволяють розділяти великий Rust-проєкт на зрозумілі частини.
Error handling
Rust не використовує exceptions у звичному стилі.
Основні підходи:
- `Result<T, E>` для recoverable errors;
- `Option<T>` для відсутності значення;
- `panic!` для unrecoverable errors;
- `?` для передачі помилок;
- crates на кшталт `thiserror` і `anyhow`.
Важливо: у production-коді краще повертати `Result`, а не покладатися на `panic!` для звичайних помилок.
Concurrency
Rust має сильну модель concurrency.
Завдяки ownership і type system Rust допомагає уникати data races.
Concurrency у Rust може включати:
- threads;
- channels;
- mutex;
- atomic types;
- async/await;
- task runtimes;
- message passing;
- shared state із контролем доступу.
Перевага: Rust дозволяє писати concurrent code з сильнішими гарантіями безпеки, ніж багато низькорівневих мов.
Threads
Rust підтримує потоки через стандартну бібліотеку.
Приклад:
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("Hello from thread");
});
handle.join().unwrap();
}
Практична роль: threads дозволяють виконувати код паралельно, але доступ до shared state потрібно контролювати.
Arc і Mutex
`Arc` і `Mutex` часто використовуються для безпечного спільного стану між потоками.
Приклад:
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 counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut value = counter.lock().unwrap();
*value += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("{}", *counter.lock().unwrap());
}
Увага: `Arc<Mutex<T>>` корисний, але надмірне використання shared mutable state може ускладнювати програму.
Async Rust
Async Rust використовується для асинхронного програмування.
Ключові елементи:
- `async`;
- `await`;
- futures;
- async runtime;
- Tokio;
- async networking;
- async web frameworks.
Приклад:
async fn fetch_data() -> String {
String::from("data")
}
Для запуску async-коду зазвичай потрібен runtime, наприклад Tokio.
Практична роль: async Rust корисний для високонавантажених мережевих сервісів, де потрібно обробляти багато одночасних операцій вводу-виводу.
Tokio
Tokio — популярний async runtime для Rust.
Tokio використовується для:
- мережевих сервісів;
- async I/O;
- HTTP-серверів;
- TCP/UDP;
- background tasks;
- timers;
- channels;
- високонавантажених backend-систем.
Приклад:
#[tokio::main]
async fn main() {
println!("Async Rust");
}
Практична роль: Tokio є одним із ключових інструментів для production async Rust.
Web development
Rust використовується для backend-розробки.
Популярні web frameworks:
- Axum;
- Actix Web;
- Rocket;
- Warp;
- Tide.
Приклад сфер застосування:
- REST API;
- microservices;
- high-performance backend;
- WebSocket-сервіси;
- API gateways;
- internal tools;
- edge services.
Практична роль: Rust може бути хорошим вибором для backend-сервісів, де важливі продуктивність, надійність і контроль ресурсів.
CLI-інструменти
Rust дуже популярний для створення command-line tools.
Причини:
- швидкий запуск;
- один binary;
- хороша продуктивність;
- зручний Cargo;
- кросплатформеність;
- бібліотеки для аргументів, конфігурації й терміналу.
Популярні crates:
- `clap`;
- `structopt`;
- `anyhow`;
- `thiserror`;
- `serde`;
- `tokio`;
- `tracing`.
Перевага для CLI: Rust дозволяє створювати швидкі й надійні інструменти, які легко поширювати як один виконуваний файл.
WebAssembly
WebAssembly або WASM — це формат виконання, який дозволяє запускати код у браузері та інших середовищах.
Rust добре підходить для WebAssembly.
Сценарії:
- web apps;
- high-performance browser logic;
- plugins;
- sandboxed execution;
- cross-platform modules;
- edge computing;
- game logic;
- cryptography in browser.
Практична роль: Rust + WebAssembly дозволяє переносити продуктивний код у браузер або sandboxed environment.
Embedded Rust
Embedded Rust — це використання Rust для мікроконтролерів і вбудованих систем.
Rust в embedded може бути корисний завдяки:
- memory safety;
- no_std;
- контролю ресурсів;
- відсутності garbage collector;
- strong typing;
- безпечнішим abstractions;
- можливості працювати близько до hardware.
Перевага Embedded Rust: він намагається дати безпечніші абстракції для low-level коду без втрати контролю над ресурсами.
no_std
no_std — це режим Rust без стандартної бібліотеки.
Використовується для:
- embedded;
- kernels;
- bootloaders;
- bare-metal;
- constrained environments;
- операційних систем;
- firmware.
Приклад:
#![no_std]
Важливо: no_std не означає “без Rust”. Це означає, що код не використовує стандартну бібліотеку, яка залежить від операційної системи.
FFI і взаємодія з C
Rust може взаємодіяти з C через FFI.
FFI використовується для:
- виклику C-бібліотек;
- створення Rust-бібліотек для C;
- інтеграції з legacy code;
- системних API;
- embedded SDK;
- high-performance modules.
Приклад оголошення зовнішньої C-функції:
extern "C" {
fn abs(input: i32) -> i32;
}
Критично: FFI часто потребує `unsafe`, тому межі між Rust і C потрібно ретельно перевіряти.
Макроси
Rust має потужну систему макросів.
Приклади:
- `println!`;
- `vec!`;
- `format!`;
- procedural macros;
- derive macros.
Приклад derive:
#[derive(Debug)]
struct User {
id: u32,
name: String,
}
Увага: макроси Rust потужні, але їх варто використовувати там, де вони справді спрощують код, а не приховують логіку.
Serde
Serde — одна з найпопулярніших Rust-бібліотек для серіалізації й десеріалізації.
Serde використовується для:
- JSON;
- YAML;
- TOML;
- MessagePack;
- API;
- конфігурацій;
- збереження даних;
- обміну повідомленнями.
Приклад:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
}
Практична роль: Serde є стандартним вибором для роботи з форматами даних у Rust-проєктах.
Testing
Rust має вбудовану підтримку тестування.
Приклад:
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
Запуск тестів:
cargo test
Практична роль: Cargo робить тестування частиною стандартного Rust workflow.
rustfmt
rustfmt — інструмент форматування Rust-коду.
Запуск:
cargo fmt
rustfmt допомагає:
- підтримувати єдиний стиль;
- зменшувати суперечки про формат;
- покращувати читабельність;
- автоматизувати formatting у CI.
Практична роль: rustfmt допомагає команді писати код в одному стилі без ручного форматування.
Clippy
Clippy — linter для Rust.
Запуск:
cargo clippy
Clippy допомагає знаходити:
- неідіоматичний код;
- потенційні помилки;
- зайві clones;
- неправильні patterns;
- simplifications;
- performance issues;
- readability issues.
Порада: `cargo clippy` варто запускати регулярно, особливо перед pull request.
Rust і C
Rust часто порівнюють із C.
| Критерій | Rust | C |
|---|---|---|
| Memory safety | Сильні перевірки компілятора | Переважно відповідальність програміста |
| Garbage collector | Немає | Немає |
| Ownership | Вбудована модель ownership | Ручна дисципліна |
| Tooling | Cargo, rustfmt, Clippy | Залежить від toolchain |
| Складність | Вища концептуально на старті | Простий синтаксис, складна пам’ять |
| Legacy | Молодша екосистема | Величезна legacy-база |
Висновок: C дає прямий контроль і величезну legacy-сумісність, а Rust додає сильніші compile-time гарантії безпеки.
Rust і C++
Rust також порівнюють із C++.
| Критерій | Rust | C++ |
|---|---|---|
| Memory safety | Ownership і borrow checker | RAII, smart pointers, але багато небезпечних можливостей |
| Абстракції | Traits, generics, enums | Classes, templates, inheritance, RAII |
| Tooling | Єдиний Cargo workflow | Різні build systems і package managers |
| Складність | Складний borrow checker | Дуже велика й складна мова |
| Performance | Висока | Висока |
Висновок: Rust і C++ обидві підходять для high-performance systems, але Rust більше фокусується на memory safety через типову систему й borrow checker.
Rust і Go
Rust і Go часто порівнюють у backend-розробці.
| Критерій | Rust | Go |
|---|---|---|
| Memory management | Ownership без GC | Garbage collector |
| Простота старту | Складніший | Простіший |
| Performance | Дуже висока | Висока |
| Concurrency | Безпечна модель + async/threading | Goroutines і channels |
| Типові задачі | Systems, performance, embedded, WASM, backend | Cloud services, microservices, DevOps tools |
Висновок: Go часто простіший для cloud-сервісів, а Rust краще підходить там, де критичні контроль ресурсів, performance і memory safety.
Rust і Python
Rust і Python мають дуже різні ролі.
| Критерій | Rust | Python |
|---|---|---|
| Виконання | Компіляція | Інтерпретований runtime |
| Швидкість розробки | Повільніший старт, суворіший компілятор | Дуже швидке прототипування |
| Performance | Дуже висока | Нижча для CPU-bound задач |
| Memory management | Ownership | Garbage collector |
| Типові задачі | Systems, backend, CLI, WASM, embedded | Data science, scripting, web, automation, AI |
Практичний висновок: Python зручний для швидких сценаріїв і data science, а Rust — для продуктивних, надійних і системних компонентів.
Переваги Rust
Основні переваги Rust:
- memory safety;
- відсутність garbage collector;
- висока продуктивність;
- сильна типізація;
- borrow checker;
- безпечніша concurrency;
- Cargo;
- хороше tooling;
- pattern matching;
- потужні enums;
- traits і generics;
- зручна обробка помилок через Result;
- придатність для системного програмування;
- WASM;
- embedded;
- FFI з C.
Головна перевага: Rust дозволяє писати продуктивний низькорівневий код із сильнішими гарантіями безпеки, ніж традиційні системні мови.
Обмеження Rust
Rust має обмеження.
Можливі складнощі:
- високий поріг входу;
- складність ownership і lifetimes;
- довший час компіляції;
- async Rust може бути складним;
- не всі бібліотеки такі зрілі, як у C/C++ або Python;
- borrow checker потребує зміни мислення;
- іноді складніше швидко написати прототип;
- unsafe все одно потрібен у низькорівневих сценаріях;
- складні generic і trait bounds можуть ускладнювати код.
Помилка: очікувати, що Rust одразу буде таким же швидким у розробці, як Python або JavaScript. Його сила — у надійності й контролі, а не в мінімальному порозі входу.
Коли варто використовувати Rust
Rust добре підходить, коли потрібні:
- висока продуктивність;
- memory safety;
- системне програмування;
- CLI tools;
- backend із високим навантаженням;
- embedded;
- WebAssembly;
- безпечна concurrency;
- криптографія;
- low-level libraries;
- FFI;
- довгострокова надійність;
- контроль ресурсів.
Практична порада: Rust варто обирати, якщо потрібні продуктивність і безпека пам’яті, а команда готова прийняти сувору модель мови.
Коли Rust може бути невдалим вибором
Rust може бути не найкращим вибором для:
- дуже швидких прототипів;
- простих CRUD-застосунків;
- команд без часу на навчання;
- задач, де Python або Go дають достатню продуктивність;
- проєктів із сильними вимогами до готових domain-бібліотек, яких у Rust ще немає;
- сценаріїв, де потрібна велика кількість junior-розробників без Rust-досвіду.
Важливо: Rust не потрібно використовувати всюди. Найбільшу цінність він дає там, де помилки пам’яті, продуктивність і надійність справді критичні.
Безпека Rust
Rust покращує безпеку пам’яті, але не вирішує всі security-проблеми.
Потрібно контролювати:
- unsafe code;
- dependency vulnerabilities;
- supply chain risks;
- cryptographic correctness;
- input validation;
- authentication;
- authorization;
- secrets;
- логування чутливих даних;
- network security;
- business logic bugs;
- panic у production;
- race conditions у зовнішніх системах.
Критично: Rust зменшує клас memory-safety bugs, але не скасовує security review, threat modeling, тестування й контроль залежностей.
Приватність даних
Rust часто використовується в системах, які працюють із важливими даними.
Необхідно контролювати:
- персональні дані;
- secrets;
- tokens;
- private keys;
- logs;
- error messages;
- telemetry;
- data retention;
- encryption;
- доступи;
- сторонні crates;
- серіалізацію даних.
Правило: навіть memory-safe мова не захищає від логічного витоку даних, якщо програма сама записує secrets у logs або передає їх не туди.
Хороші практики Rust
Рекомендовано:
- використовувати `cargo fmt`;
- запускати `cargo clippy`;
- писати tests;
- обмежувати `unsafe`;
- документувати unsafe-блоки;
- використовувати `Result` для помилок;
- не зловживати `unwrap` у production;
- уникати зайвих `clone`;
- починати із простих типів;
- використовувати traits для абстракцій;
- контролювати dependencies;
- регулярно оновлювати crates;
- додавати CI;
- перевіряти panic paths;
- писати зрозумілі error messages.
Головне правило: ідіоматичний Rust — це код, який використовує типи, ownership, Result, pattern matching і tests для явного опису коректної поведінки.
Типові помилки початківців
Поширені помилки:
- надмірна боротьба з borrow checker;
- зайве використання `clone`;
- використання `String` там, де достатньо `&str`;
- часте `unwrap` без обробки помилок;
- неправильне розуміння lifetimes;
- спроба писати Rust як C++ або Java;
- занадто складні generic types;
- ігнорування Clippy;
- страх перед `Result` і `Option`;
- надмірне використання `Arc<Mutex<T>>`;
- спроба використовувати unsafe без потреби.
Небезпека: найгірший Rust-код часто виникає тоді, коли програміст намагається обійти модель ownership замість того, щоб перепроєктувати структуру даних.
Приклади задач на Rust
Читання файлу
use std::fs;
use std::io;
fn read_config(path: &str) -> Result<String, io::Error> {
fs::read_to_string(path)
}
fn main() {
match read_config("config.toml") {
Ok(content) => println!("{}", content),
Err(error) => eprintln!("Error: {}", error),
}
}
Структура і метод
struct User {
id: u32,
name: String,
}
impl User {
fn display_name(&self) -> String {
format!("{} #{}", self.name, self.id)
}
}
fn main() {
let user = User {
id: 1,
name: String::from("Alice"),
};
println!("{}", user.display_name());
}
Обробка Option
fn find_name(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
if let Some(name) = find_name(1) {
println!("Found: {}", name);
} else {
println!("Not found");
}
}
Простий Result
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(value) => println!("{}", value),
Err(error) => eprintln!("{}", error),
}
}
Підказка: у Rust-прикладах важливо дивитися не лише на синтаксис, а й на ownership, Result, Option і borrowing.
Джерела
- Офіційний сайт Rust.
- The Rust Programming Language.
- Rust Reference.
- Rust Standard Library Documentation.
- Cargo Book.
- Rustonomicon.
- Rust by Example.
- Документація Tokio.
- Документація Serde.
- Документація crates.io.
- Документація rustfmt і Clippy.
Висновок
Rust — це сучасна системна мова програмування, яка поєднує високу продуктивність, відсутність garbage collector і сильні гарантії memory safety. Її головні концепції — ownership, borrowing, lifetimes, traits, Result, Option і pattern matching.
Rust особливо корисний для системного програмування, CLI, backend-сервісів, embedded, WebAssembly, безпечної concurrency і performance-sensitive software. Водночас Rust має вищий поріг входу, ніж багато популярних мов, і потребує зміни підходу до проєктування даних і ownership.
Головна думка: Rust — це мова для продуктивного й надійного програмування, де компілятор допомагає знаходити помилки пам’яті та concurrency ще до запуску програми.
Див. також
- Програмування
- Мова програмування
- Мова програмування C
- C++
- Go
- Python
- Системне програмування
- Низькорівневе програмування
- Memory safety
- Ownership
- Borrowing
- Cargo
- WebAssembly
- Embedded Rust
- Tokio
- Serde
- CLI
- Backend
- Безпека застосунків
- Налагодження коду
- Логування
- Алгоритм
- Структура даних