Rust: Основи для досвідчених розробників у 2026 році

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

Посібник з Rust для досвідчених розробників

Rust продовжує набирати популярність рік за роком, і на це є вагомі причини: гарантована безпека пам'яті на етапі компіляції, продуктивність рівня C++ та сучасна екосистема. Для досвідчених розробників, які приходять з C++, Java або Python, початок роботи з Rust може здатися незвичним, але фундаментальні концепції швидко стають інтуїтивними після їх засвоєння.

Чому Rust у 2026 році?

Rust утримує звання найулюбленішої мови на Stack Overflow вже 8 років поспіль. Прийнятий Microsoft, Google, Amazon та Meta для критично важливих компонентів, Rust забезпечує безпеку пам'яті без збирача сміття.

Налаштування середовища розробки Rust

Перед написанням коду необхідно встановити Rust через rustup -- офіційний інструмент керування версіями.

bash
# install.sh
# Install Rust via rustup (macOS, Linux)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Verify installation
rustc --version
cargo --version

Cargo -- це пакетний менеджер та інструмент збірки Rust. Він поєднує функціональність npm, Maven та Make в одному узгодженому інструменті.

bash
# 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 змінює звичну конвенцію: змінні є незмінними за замовчуванням. Такий підхід змушує явно продумувати мутабельність та запобігає багатьом помилкам.

variables.rsrust
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
}
Затінення (Shadowing) та мутабельність

Затінення створює нову змінну, на відміну від mut, яке змінює існуюче значення. Затінення дозволяє трансформувати значення, зберігаючи зрозуміле ім'я.

Фундаментальні типи даних

Rust -- статично типізована мова з чудовим виведенням типів. Нижче наведено основні примітивні типи, які необхідно знати.

types.rsrust
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. Ця система гарантує безпеку пам'яті без збирача сміття, ціною початкової кривої навчання.

Три правила власності

ownership.rsrust
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)

move_clone.rsrust
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
}
Переміщення та функції

Передача значення у функцію переносить власність. Функція стає новим власником, і значення стає недоступним після виклику, якщо його не повернено.

ownership_functions.rsrust
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.

borrowing.rsrust
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");
}

Правила запозичення

borrowing_rules.rsrust
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): Гарантія валідності посилань

Часи життя забезпечують збереження валідності посилань. Компілятор часто виводить їх автоматично, але іноді потрібна явна анотація.

lifetimes_basic.rsrust
// 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
}

Анотації часів життя

lifetimes_annotation.rsrust
// 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.

structs.rsrust
// 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 значно потужніші за аналоги в більшості мов: кожен варіант може містити дані.

enums.rsrust
// 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: Основи обробки помилок

option_result.rsrust
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.

traits.rsrust
// 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 надає потужні типи колекцій.

collections.rsrust
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. Нижче наведено рекомендовані патерни.

error_handling.rsrust
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 інтегрує фреймворк тестування безпосередньо в мову.

lib.rsrust
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.

Теги

#rust
#systems programming
#ownership
#memory safety
#performance

Поділитися

Пов'язані статті