Rust: Основи для досвідчених розробників у 2026 році
Швидке освоєння Rust з використанням наявного досвіду програмування. Власність, запозичення, часи життя та ключові патерни для розробників, які переходять з C++, Java або Python.

Rust продовжує набирати популярність рік за роком, і на це є вагомі причини: гарантована безпека пам'яті на етапі компіляції, продуктивність рівня C++ та сучасна екосистема. Для досвідчених розробників, які приходять з C++, Java або Python, початок роботи з Rust може здатися незвичним, але фундаментальні концепції швидко стають інтуїтивними після їх засвоєння.
Rust утримує звання найулюбленішої мови на Stack Overflow вже 8 років поспіль. Прийнятий Microsoft, Google, Amazon та Meta для критично важливих компонентів, Rust забезпечує безпеку пам'яті без збирача сміття.
Налаштування середовища розробки Rust
Перед написанням коду необхідно встановити Rust через rustup -- офіційний інструмент керування версіями.
# install.sh
# Install Rust via rustup (macOS, Linux)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify installation
rustc --version
cargo --versionCargo -- це пакетний менеджер та інструмент збірки Rust. Він поєднує функціональність npm, Maven та Make в одному узгодженому інструменті.
# project-setup.sh
# Create a new project
cargo new my_project
cd my_project
# Generated structure:
# my_project/
# ├── Cargo.toml # Manifest (like package.json)
# └── src/
# └── main.rs # Entry point
# Essential commands
cargo build # Compile the project
cargo run # Compile and run
cargo test # Run tests
cargo check # Check without building (faster)Змінні та незмінність за замовчуванням
Rust змінює звичну конвенцію: змінні є незмінними за замовчуванням. Такий підхід змушує явно продумувати мутабельність та запобігає багатьом помилкам.
fn main() {
// Immutable by default
let x = 5;
// x = 6; // Compile error!
// Explicitly mutable variable
let mut y = 5;
y = 6; // OK
// Shadowing: redeclaration in the same scope
let x = x + 1; // Creates a new variable x
let x = x * 2; // x is now 12
// Shadowing also allows changing the type
let spaces = " "; // &str
let spaces = spaces.len(); // usize
}Затінення створює нову змінну, на відміну від mut, яке змінює існуюче значення. Затінення дозволяє трансформувати значення, зберігаючи зрозуміле ім'я.
Фундаментальні типи даних
Rust -- статично типізована мова з чудовим виведенням типів. Нижче наведено основні примітивні типи, які необхідно знати.
fn main() {
// Signed integers: i8, i16, i32, i64, i128, isize
let age: i32 = 30;
// Unsigned integers: u8, u16, u32, u64, u128, usize
let count: u64 = 1_000_000; // Underscores for readability
// Floats: f32, f64 (default)
let pi: f64 = 3.14159;
// Boolean
let active: bool = true;
// Unicode character (4 bytes)
let emoji: char = '🦀';
// Tuple: fixed collection of different types
let person: (String, i32) = (String::from("Alice"), 28);
let (name, age) = person; // Destructuring
// Array: fixed size, same type
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let first = numbers[0];
// Slice: view into a portion of data
let slice: &[i32] = &numbers[1..4]; // [2, 3, 4]
}Власність (Ownership): Революційна концепція
Власність -- це головна інновація Rust. Ця система гарантує безпеку пам'яті без збирача сміття, ціною початкової кривої навчання.
Три правила власності
fn main() {
// Rule 1: Each value has a single owner
let s1 = String::from("hello");
// Rule 2: When the owner goes out of scope, the value is freed
{
let s2 = String::from("world");
// s2 is valid here
}
// s2 is dropped, no longer accessible
// Rule 3: Only one ownership at a time (move)
let s3 = s1; // s1 is MOVED to s3
// println!("{}", s1); // Error: s1 is no longer valid!
println!("{}", s3); // OK: s3 is the owner
}Переміщення (Move) та клонування (Clone)
fn main() {
// Simple types (stack): automatic Copy
let x = 5;
let y = x; // Copy, not move
println!("x = {}, y = {}", x, y); // Both are valid
// Complex types (heap): Move by default
let s1 = String::from("hello");
let s2 = s1; // Move
// s1 is no longer usable
// Explicit clone to duplicate
let s3 = String::from("world");
let s4 = s3.clone(); // Deep copy
println!("s3 = {}, s4 = {}", s3, s4); // Both valid
}Передача значення у функцію переносить власність. Функція стає новим власником, і значення стає недоступним після виклику, якщо його не повернено.
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // Error: s has been moved
let x = 5;
makes_copy(x);
println!("{}", x); // OK: i32 implements Copy
// To regain ownership, return the value
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("{}", s3); // OK
}
fn takes_ownership(s: String) {
println!("{}", s);
} // s is dropped here
fn makes_copy(x: i32) {
println!("{}", x);
}
fn takes_and_gives_back(s: String) -> String {
s // Returns ownership
}Запозичення (Borrowing): Посилання без передачі власності
Запозичення дозволяє використовувати значення без отримання власності. Це найчастіше використовуваний механізм у Rust.
fn main() {
let s1 = String::from("hello");
// Immutable reference: read-only
let len = calculate_length(&s1);
println!("Length of '{}': {}", s1, len); // s1 still valid
// Mutable reference: modification allowed
let mut s2 = String::from("hello");
change(&mut s2);
println!("{}", s2); // "hello, world"
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope but doesn't drop (it's a reference)
fn change(s: &mut String) {
s.push_str(", world");
}Правила запозичення
fn main() {
let mut s = String::from("hello");
// Rule 1: Multiple immutable references simultaneously OK
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// Rule 2: ONLY ONE mutable reference at a time
let r3 = &mut s;
// let r4 = &mut s; // Error: already borrowed mutably
println!("{}", r3);
// Rule 3: No mutable ref if immutable ref exists
let r5 = &s;
// let r6 = &mut s; // Error: r5 is still active
println!("{}", r5);
// Once r5 is used for the last time, mutable borrow is allowed
let r7 = &mut s; // OK: r5 is no longer used after this
r7.push_str("!");
}Готовий до співбесід з Rust?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Часи життя (Lifetimes): Гарантія валідності посилань
Часи життя забезпечують збереження валідності посилань. Компілятор часто виводить їх автоматично, але іноді потрібна явна анотація.
// Classic error: reference to freed data
// fn dangling() -> &String {
// let s = String::from("hello");
// &s // Error: s will be dropped, invalid reference!
// }
// Solution: return the owned value
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership transferred, no problem
}Анотації часів життя
// The compiler can't figure out which reference will be returned
// fn longest(x: &str, y: &str) -> &str { ... } // Error!
// Explicit annotation: return lives as long as BOTH x AND y
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("Longest: {}", result); // OK here
}
// println!("{}", result); // Error: string2 dropped
}Rust застосовує правила елізії, що позбавляють необхідності анотування простих випадків. На початковому етапі достатньо слідувати явним повідомленням компілятора.
Структури (Structs) та реалізації (Implementations)
Структури -- це базові будівельні блоки для створення власних типів у Rust.
// Struct definition
#[derive(Debug)] // Enables printing with {:?}
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
// Implementation block for methods
impl User {
// Constructor (convention: fn new or descriptive name)
fn new(username: String, email: String) -> Self {
Self {
username,
email,
active: true,
sign_in_count: 1,
}
}
// Method: takes &self (reference to instance)
fn is_active(&self) -> bool {
self.active
}
// Method with mutation: takes &mut self
fn deactivate(&mut self) {
self.active = false;
}
// Method consuming self (rare)
fn into_username(self) -> String {
self.username
}
}
fn main() {
let mut user = User::new(
String::from("alice"),
String::from("alice@example.com"),
);
println!("Active: {}", user.is_active());
user.deactivate();
println!("Active: {}", user.is_active());
// Debug print
println!("{:?}", user);
}Перерахування (Enums) та зіставлення зі зразком (Pattern Matching)
Перерахування в Rust значно потужніші за аналоги в більшості мов: кожен варіант може містити дані.
// Simple enum
enum Direction {
North,
South,
East,
West,
}
// Enum with data (algebraic data type)
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
impl Message {
fn process(&self) {
match self {
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Color: rgb({}, {}, {})", r, g, b)
}
}
}
}
fn main() {
let msg = Message::Move { x: 10, y: 20 };
msg.process();
let msg2 = Message::Write(String::from("Hello Rust!"));
msg2.process();
}Option та Result: Основи обробки помилок
use std::fs::File;
use std::io::{self, Read};
fn main() {
// Option<T>: present or absent value (replaces null)
let numbers = vec![1, 2, 3];
let first: Option<&i32> = numbers.first();
match first {
Some(n) => println!("First: {}", n),
None => println!("Empty list"),
}
// Utility methods
let value = first.unwrap_or(&0);
let doubled = first.map(|n| n * 2);
// Result<T, E>: success or error
let result = read_file("config.txt");
match result {
Ok(content) => println!("Content: {}", content),
Err(e) => println!("Error: {}", e),
}
}
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // ? propagates the error
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}Оператор ? -- це синтаксичний цукор для поширення помилок. Він автоматично повертає помилку, якщо Result є Err, інакше розгортає значення Ok.
Трейти (Traits): Поліморфізм у стилі Rust
Трейти визначають спільну поведінку, подібно до інтерфейсів у Java або протоколів у Swift.
// Trait definition
trait Summary {
fn summarize(&self) -> String;
// Method with default implementation
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..50.min(self.summarize().len())])
}
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
// Implementation for Article
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
// Implementation for Tweet
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
// Function accepting any type implementing Summary
fn notify(item: &impl Summary) {
println!("Breaking news: {}", item.summarize());
}
// Equivalent syntax with trait bound
fn notify_generic<T: Summary>(item: &T) {
println!("Breaking news: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 2026"),
author: String::from("Community"),
content: String::from("..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
content: String::from("Rust is awesome!"),
};
notify(&article);
notify(&tweet);
}Основні колекції
Стандартна бібліотека Rust надає потужні типи колекцій.
use std::collections::HashMap;
fn main() {
// Vec<T>: dynamic array
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
// vec! macro for initialization
let nums = vec![1, 2, 3, 4, 5];
// Iteration
for n in &nums {
println!("{}", n);
}
// Functional methods
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
let sum: i32 = nums.iter().sum();
let evens: Vec<&i32> = nums.iter().filter(|x| *x % 2 == 0).collect();
// String: growable UTF-8 string
let mut s = String::from("Hello");
s.push_str(", World!");
s.push('!');
// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1 moved, s2 borrowed
// or with format!
let s4 = format!("{}{}", "Hello, ", "World!");
// HashMap<K, V>
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Access with get (returns Option)
if let Some(score) = scores.get("Blue") {
println!("Blue: {}", score);
}
// Entry API for conditional insertion
scores.entry(String::from("Yellow")).or_insert(25);
}Ідіоматична обробка помилок у Rust
Правильна обробка помилок має критичне значення в Rust. Нижче наведено рекомендовані патерни.
use std::fs::File;
use std::io::{self, Read};
use std::num::ParseIntError;
// Define a custom error type
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
Custom(String),
}
// Implement From for automatic conversion
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
AppError::Io(err)
}
}
impl From<ParseIntError> for AppError {
fn from(err: ParseIntError) -> Self {
AppError::Parse(err)
}
}
// Function returning Result with custom error
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
let mut file = File::open(path)?; // io::Error -> AppError
let mut content = String::new();
file.read_to_string(&mut content)?;
let number: i32 = content.trim().parse()?; // ParseIntError -> AppError
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(n) => println!("Number: {}", n),
Err(AppError::Io(e)) => println!("IO error: {}", e),
Err(AppError::Parse(e)) => println!("Parse error: {}", e),
Err(AppError::Custom(msg)) => println!("Error: {}", msg),
}
}Для реальних проєктів крейт thiserror (для бібліотек) та anyhow (для застосунків) значно спрощують обробку помилок.
Тестування в Rust
Rust інтегрує фреймворк тестування безпосередньо в мову.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
// Test module (compiled only for `cargo test`)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10, 2), Ok(5));
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10, 0).is_err());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panic!
}
}Висновок
Rust пропонує унікальну парадигму, що поєднує безпеку пам'яті та продуктивність. Концепції власності та запозичення спочатку здаються обмежувальними, але з практикою стають природними. Компілятор Rust -- безцінний помічник: його повідомлення про помилки вважаються одними з найкращих у галузі.
Контрольний список для початку
- Встановлення Rust через rustup та опанування Cargo
- Розуміння різниці між незмінністю та явною мутабельністю
- Засвоєння трьох правил власності
- Практика запозичення з посиланнями
&та&mut - Використання
OptionтаResultзамість null та виключень - Написання тестів за допомогою
#[test]
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Спільнота Rust привітна, а ресурсів достатньо. Офіційна книга "The Rust Programming Language" доступна безкоштовно онлайн. Маючи ці надійні основи, можна переходити до поглиблених тем: async/await, макроси та WebAssembly.
Теги
Поділитися
Пов'язані статті

Ownership і Borrowing у Rust: Повний Посібник
Опануйте систему ownership і borrowing у Rust. Правила власності, посилання, lifetime та просунуті патерни управління пам'яттю.

Zapytannia na spivbesidi z Rust: Povnyi posibnyk 2026
25 naiposhyrenishykh zapytan na spivbesidi z Rust. Vlastnist, zapozychennia, chasy zhyttia, treity, async/await, paralelizm z detalianymy vidpovidyamy ta prykladamy kodu.

Ownership i Borrowing у Rust: повний посібник з прикладами коду
Детальний розбір ownership та borrowing у Rust з практичними прикладами. Семантика переміщення, посилання, часи життя та borrow checker для безпечного керування пам'яттю.