# Ownership і Borrowing у Rust: Повний Посібник > Опануйте систему ownership і borrowing у Rust. Правила власності, посилання, lifetime та просунуті патерни управління пам'яттю. - Published: 2026-02-20 - Updated: 2026-04-29 - Author: SharpSkill - Tags: rust, ownership, borrowing, memory management, systems programming - Reading time: 18 min --- Система ownership — це те, що відрізняє Rust від будь-якої іншої мови програмування. Цей унікальний підхід гарантує безпеку пам'яті без збирача сміття, виявляючи помилки під час компіляції замість часу виконання. Цей детальний посібник досліджує механізми ownership і borrowing від основ до просунутих виробничих патернів. > **Філософія Rust** > > Компілятор Rust діє як вимогливий помічник програміста: кожна помилка ownership, заблокована під час компіляції, означає потенційний баг, уникнутий у продакшені. ## Три Фундаментальні Правила Ownership Система ownership ґрунтується на трьох простих, але суворих правилах. Після їх засвоєння ментальна модель Rust стає природною та передбачуваною. ```rust // ownership_rules.rs // Demonstration of the three fundamental rules fn main() { // Rule 1: Each value has exactly ONE owner let s1 = String::from("hello"); // s1 is the sole owner // Rule 2: There can only be one owner at a time let s2 = s1; // Ownership transferred (moved) from s1 to s2 // println!("{}", s1); // Compile ERROR: s1 no longer exists println!("s2 = {}", s2); // Only s2 is valid now // Rule 3: When the owner goes out of scope, the value is dropped { let s3 = String::from("temporary"); println!("s3 inside block = {}", s3); } // s3 is automatically freed here (drop is called) // println!("{}", s3); // ERROR: s3 no longer exists } ``` Ці три правила усувають цілі категорії багів: use-after-free, double-free та витоки пам'яті. Компілятор статично перевіряє дотримання цих правил. ## Move vs Copy: Розуміння Семантики Передачі Поведінка присвоєння залежить від типу даних. Типи, що реалізують трейт Copy, дублюються, тоді як інші передаються через move. ```rust // move_vs_copy.rs // Distinction between Copy types and Move types fn main() { // Copy types: values stored on the stack, known size let x: i32 = 42; let y = x; // x is COPIED, not moved println!("x = {}, y = {}", x, y); // Both are valid // Other Copy types: f64, bool, char, tuples of Copy types let point = (3.0, 4.0); let point_copy = point; // Tuple copy println!("Original: {:?}, Copy: {:?}", point, point_copy); // Move types: values on the heap, dynamic size let s1 = String::from("owned"); let s2 = s1; // s1 is MOVED to s2 // println!("{}", s1); // ERROR: value moved println!("s2 = {}", s2); // Vec, HashMap, Box are also Move types let vec1 = vec![1, 2, 3]; let vec2 = vec1; // Move, not copy // println!("{:?}", vec1); // ERROR println!("vec2 = {:?}", vec2); } // Explicit clone to duplicate Move types fn explicit_clone() { let original = String::from("important data"); let clone = original.clone(); // Explicit duplication (memory cost) println!("Original: {}", original); // Still valid println!("Clone: {}", clone); // Independent copy } ``` Розрізнення Move/Copy є фундаментальним: воно визначає, чи передає присвоєння власність, чи створює незалежну копію. > **Коли Використовувати Clone** > > Виклик `.clone()` має бути усвідомленим. Код, переповнений клонами, може вказувати на проблему дизайну. Borrowing часто є кращим рішенням. ## Borrowing: Незмінні та Змінні Посилання Borrowing дозволяє доступ до значення без отримання його власності. Цей механізм робить код Rust одночасно безпечним і продуктивним. ```rust // borrowing_basics.rs // Immutable and mutable references fn main() { let s = String::from("hello"); // Immutable reference: read-only, multiple allowed let len = calculate_length(&s); // Immutable borrow println!("'{}' has {} characters", s, len); // s still valid // Multiple simultaneous immutable references: OK let r1 = &s; let r2 = &s; let r3 = &s; println!("r1={}, r2={}, r3={}", r1, r2, r3); } fn calculate_length(s: &String) -> usize { // s is a reference, not the owner s.len() } // s goes out of scope but doesn't drop anything (not owner) // Mutable references: modification allowed fn mutable_borrowing() { let mut s = String::from("hello"); change(&mut s); // Mutable borrow println!("After modification: {}", s); } fn change(s: &mut String) { s.push_str(", world!"); // Modification via mutable reference } ``` Золоте правило borrowing: або декілька незмінних посилань АБО одне змінне посилання, ніколи обидва одночасно. ## Правила Borrow Checker Borrow checker — це компонент компілятора, який перевіряє правила borrowing. Розуміння його помилок дозволяє швидко вирішувати проблеми. ```rust // borrow_checker_rules.rs // Strict borrow checker rules fn main() { // RULE 1: No mutable reference with immutable references let mut s = String::from("hello"); let r1 = &s; // Immutable reference: OK let r2 = &s; // Another immutable reference: OK // let r3 = &mut s; // ERROR: cannot borrow as mutable println!("{} and {}", r1, r2); // AFTER using r1 and r2, they are "dead" let r3 = &mut s; // Now OK: r1 and r2 no longer used r3.push_str(" world"); println!("{}", r3); // RULE 2: Only one mutable reference at a time let mut data = String::from("exclusive"); let ref1 = &mut data; // let ref2 = &mut data; // ERROR: already borrowed mutably ref1.push_str("!"); println!("{}", ref1); } // RULE 3: References cannot outlive the data fn dangling_reference_prevented() { let reference; { let s = String::from("short-lived"); // reference = &s; // ERROR: s doesn't live long enough } // s is dropped here, reference would be invalid // Solution: move the value out of the scope let owned_outside; { let s = String::from("moved out"); owned_outside = s; // Move, not reference } println!("{}", owned_outside); // OK: owned_outside is the owner } ``` Borrow checker використовує Non-Lexical Lifetimes (NLL): посилання вважається активним лише до останнього використання, а не до кінця scope. ## Lifetimes: Анотування Тривалості Посилань Lifetimes — це анотації, які допомагають компілятору перевірити, що посилання залишаються валідними. Здебільшого вони визначаються автоматично. ```rust // lifetimes_explained.rs // Explicit lifetime annotations // Without annotation: compiler infers lifetimes fn first_word(s: &str) -> &str { match s.find(' ') { Some(i) => &s[..i], None => s, } } // With explicit annotation: same function fn first_word_explicit<'a>(s: &'a str) -> &'a str { // 'a means: returned reference lives as long as the input match s.find(' ') { Some(i) => &s[..i], None => s, } } // When annotations are necessary: multiple references fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { // Compiler cannot guess which reference is returned 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 if uncommented: string2 dropped } ``` Lifetimes не змінюють тривалість життя даних; вони описують відношення між тривалістю життя різних посилань. ## Lifetimes у Структурах Коли структура містить посилання, lifetimes повинні бути анотовані, щоб гарантувати, що структура не переживає дані, на які посилається. ```rust // struct_lifetimes.rs // Structs containing references // Struct with reference: lifetime required struct ImportantExcerpt<'a> { part: &'a str, // This reference must live at least as long as the struct } impl<'a> ImportantExcerpt<'a> { // Method returning a reference with the same lifetime fn level(&self) -> i32 { 3 } // Elision rule: &self implies the output lifetime fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention: {}", announcement); self.part // Returns with 'a lifetime from self } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap(); let excerpt = ImportantExcerpt { part: first_sentence, // OK: novel outlives excerpt }; println!("Excerpt: {}", excerpt.part); println!("Level: {}", excerpt.level()); } // Static lifetime: reference valid for the entire program duration fn static_lifetime_example() { let s: &'static str = "This string is in the binary"; // String literals always have 'static lifetime println!("{}", s); } ``` Правила elision lifetimes часто дозволяють опускати анотації у поширених випадках, роблячи код більш читабельним. ## Просунуті Патерни: Внутрішня Мутабельність Іноді мутабельність повинна перевірятися під час виконання, а не компіляції. Rust надає типи для цього патерну: RefCell і Cell. ```rust // interior_mutability.rs // Interior mutability with RefCell and Cell use std::cell::{Cell, RefCell}; // Cell: for Copy types, replaces the entire value struct Counter { count: Cell, // Mutable despite &self } impl Counter { fn new() -> Counter { Counter { count: Cell::new(0) } } fn increment(&self) { // Modification via immutable reference! self.count.set(self.count.get() + 1); } fn get(&self) -> u32 { self.count.get() } } // RefCell: for non-Copy types, checks at runtime struct CachedValue { value: RefCell>, } impl CachedValue { fn new() -> CachedValue { CachedValue { value: RefCell::new(None) } } fn get_or_compute(&self, compute: impl FnOnce() -> String) -> String { // borrow() for reading, borrow_mut() for writing if self.value.borrow().is_none() { *self.value.borrow_mut() = Some(compute()); } self.value.borrow().as_ref().unwrap().clone() } } fn main() { let counter = Counter::new(); counter.increment(); counter.increment(); println!("Counter: {}", counter.get()); // 2 let cache = CachedValue::new(); let result = cache.get_or_compute(|| { println!("Expensive computation..."); String::from("result") }); println!("Value: {}", result); // Second call: no recomputation let result2 = cache.get_or_compute(|| String::from("never executed")); println!("Cache hit: {}", result2); } ``` RefCell і Cell переносять перевірку borrowing на час виконання. Порушення правила спричиняє panic замість помилки компіляції. > **Обережно з Panic** > > `RefCell::borrow_mut()` спричиняє panic, якщо значення вже позичене. Варто використовувати `try_borrow_mut()` для явної обробки помилок. ## Smart Pointers і Ownership Smart pointers, такі як Box, Rc та Arc, пропонують різні стратегії ownership для конкретних випадків використання. ```rust // smart_pointers.rs // Box, Rc, and Arc for different ownership patterns use std::rc::Rc; use std::sync::Arc; use std::thread; // Box: single owner, data on the heap fn box_example() { let boxed = Box::new(vec![1, 2, 3, 4, 5]); println!("Boxed vec: {:?}", boxed); // Useful for: recursive types, large objects, trait objects } // Rc: reference counting, multiple owners (single-thread) fn rc_example() { let data = Rc::new(String::from("shared data")); let clone1 = Rc::clone(&data); // Increments the counter let clone2 = Rc::clone(&data); println!("Count: {}", Rc::strong_count(&data)); // 3 println!("All share: {}, {}, {}", data, clone1, clone2); } // Freed when counter reaches 0 // Arc: thread-safe Rc (Atomic Reference Counting) fn arc_example() { let data = Arc::new(vec![1, 2, 3]); let handles: Vec<_> = (0..3).map(|i| { let data_clone = Arc::clone(&data); thread::spawn(move || { println!("Thread {}: {:?}", i, data_clone); }) }).collect(); for handle in handles { handle.join().unwrap(); } } fn main() { box_example(); rc_example(); arc_example(); } ``` Вибір smart pointer залежить від патерну ownership: унікальний (Box), спільний у одному потоці (Rc) або спільний між потоками (Arc). ## Практичні Патерни Ownership Ось поширені патерни для структурування коду навколо системи ownership. ```rust // ownership_patterns.rs // Practical patterns for ownership management // Pattern 1: Builder pattern with chained ownership struct RequestBuilder { url: String, headers: Vec<(String, String)>, timeout: Option, } impl RequestBuilder { fn new(url: &str) -> Self { RequestBuilder { url: url.to_string(), headers: Vec::new(), timeout: None, } } // Consumes self and returns the new self fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self // Returns ownership } fn timeout(mut self, seconds: u64) -> Self { self.timeout = Some(seconds); self } fn build(self) -> Request { Request { url: self.url, headers: self.headers, timeout: self.timeout.unwrap_or(30), } } } struct Request { url: String, headers: Vec<(String, String)>, timeout: u64, } // Pattern 2: Cow (Copy-on-Write) to avoid allocations use std::borrow::Cow; fn process_text(input: &str) -> Cow { if input.contains("REPLACE") { // Allocation only if modification needed Cow::Owned(input.replace("REPLACE", "NEW")) } else { // No allocation, returns a reference Cow::Borrowed(input) } } // Pattern 3: Take to extract from an Option fn extract_value(data: &mut Option) -> String { data.take().unwrap_or_else(|| String::from("default")) // take() replaces with None and returns ownership of the value } fn main() { // Builder pattern let request = RequestBuilder::new("https://api.example.com") .header("Authorization", "Bearer token") .header("Content-Type", "application/json") .timeout(60) .build(); println!("URL: {}, Timeout: {}s", request.url, request.timeout); // Cow pattern let text1 = process_text("hello world"); // No allocation let text2 = process_text("hello REPLACE"); // Allocation println!("{} | {}", text1, text2); // Take pattern let mut optional = Some(String::from("extracted")); let value = extract_value(&mut optional); println!("Value: {}, Option: {:?}", value, optional); // None } ``` Ці патерни використовують систему ownership для створення ергономічних і продуктивних API. ## Висновок Система ownership і borrowing у Rust представляє зміну парадигми в управлінні пам'яттю. Після опанування вона стає потужним союзником для написання коду, який є одночасно продуктивним і безпечним. **Ключові моменти:** ✅ Три правила ownership: єдиний власник, передача власності, автоматичний drop ✅ Borrowing: декілька незмінних посилань АБО одне ексклюзивне змінне посилання ✅ Lifetimes: анотування відношень між тривалостями життя посилань ✅ Внутрішня мутабельність: RefCell і Cell для перевірки мутабельності під час виконання ✅ Smart pointers: Box (унікальний), Rc (спільний), Arc (thread-safe) ✅ Практичні патерни: Builder, Cow, Take для ідіоматичних API Borrow checker може здаватися суворим спочатку, але кожна повідомлена помилка означає потенційно уникнутий баг. З практикою мислення в термінах ownership стає природним і покращує якість коду в усіх мовах. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/rust/ownership-borrowing-rust-complete-guide