# Розумні вказівники в Rust: Box, Rc, Arc і RefCell у 2026 > Розумні вказівники Rust Box, Rc, Arc і RefCell з поясненнями, компільованими прикладами 2026 року, таблицею вибору та типовими питаннями співбесід. - Published: 2026-07-05 - Updated: 2026-07-07 - Author: SharpSkill - Tags: rust, smart-pointers, memory-management, interview, rust-2024-edition - Reading time: 11 min --- Розумні вказівники в Rust — це інструменти, що відкривають шаблони володіння, які сам borrow checker виразити не здатен: розміщення в купі, спільне володіння та зміну за спільним посиланням. Тоді як звичайне посилання (`&T`) лише позичає значення, розумний вказівник володіє своїми даними й додає до них додаткову поведінку. Цей посібник розбирає чотири типи, з якими кожен розробник на Rust стикається у продакшені та на співбесідах: `Box`, `Rc`, `Arc` і `RefCell`, разом із компільованими прикладами для редакції Rust 2024. > **Стисле резюме в один рядок** > > `Box` застосовують для розміщення в купі з одним власником, `Rc` — для спільного володіння в межах одного потоку, `Arc` — для спільного володіння між потоками, а `RefCell` — для зміни значення через спільне посилання. Пари `Rc>` і `Arc>` покривають спільний змінюваний стан. ## Що таке розумні вказівники в Rust Розумний вказівник — це структура, що поводиться як вказівник, але несе додаткові метадані чи можливості. Більшість реалізують трейт `Deref`, тож `*pointer` і виклики методів працюють так, ніби вказівник є звичайним посиланням, а також трейт `Drop`, тож очищення відбувається автоматично, коли значення виходить із області видимості. Стандартна бібліотека постачає чотири типи, розглянуті тут, а їх розуміння спирається на впевнене володіння темою [володіння і позичання](/blog/rust/ownership-borrowing-rust-complete-guide), що вирішує, хто і коли звільняє кожне виділення. Ключова відмінність від посилання — це володіння. `&T` ніколи не володіє даними, на які вказує, тож не може пережити значення. Розумний вказівник володіє даними, керує їхнім часом життя та звільняє їх детерміновано. [Розділ книги Rust про розумні вказівники](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) розглядає їх як категорію саме тому, що вони мають спільну форму: володіння плюс поведінка. ## `Box`: розміщення в купі для рекурсивних типів і типів із відомим розміром `Box` — найпростіший розумний вказівник. Він зберігає значення в купі та тримає вказівник на нього у стеку, має одного власника й не додає накладних витрат під час виконання, окрім самого виділення. Його найпоширеніше завдання — надати рекурсивним типам відомий розмір: тип, що містить сам себе безпосередньо, був би нескінченно великим, але `Box` — це лише вказівник, тож його розмір фіксований незалежно від того, на що він вказує. Наведений нижче приклад визначає бінарне дерево. Кожен `Node` тримає двох нащадків, і без непрямості компілятор не може обчислити розмір `Tree`. Загортання кожного нащадка в `Box` розриває рекурсію на рівні типу. ```rust // tree.rs #[derive(Debug)] enum Tree { Leaf(i32), // Box puts each child on the heap, so Node has a fixed size (two pointers). Node(Box, Box), } fn sum(tree: &Tree) -> i32 { match tree { Tree::Leaf(value) => *value, // base case: return the leaf Tree::Node(left, right) => sum(left) + sum(right), // recurse into both children } } fn main() { // Build (1) + ((2) + (3)) = 6 let tree = Tree::Node( Box::new(Tree::Leaf(1)), Box::new(Tree::Node( Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)), )), ); println!("sum = {}", sum(&tree)); // sum = 6 } ``` `Box` також важливий, коли переміщення великого значення було б витратним або коли повертається трейт-об'єкт, як-от `Box`. У кожному випадку правило те саме: один власник, який звільняється автоматично, коли `Box` знищується. ## `Rc`: спільне володіння в однопотоковому коді Іноді значенню потрібно кілька власників, жоден з яких не є очевидно останнім, хто його використовує. `Rc` (з підрахунком посилань) розв'язує це, тримаючи сильний лічильник наявних власників. `Rc::clone` збільшує цей лічильник, не копіюючи базові дані, а кожне знищення його зменшує. Коли лічильник досягає нуля, значення звільняється. Прикладом є об'єкт конфігурації, який читають кілька робітників. Кожен робітник має тримати конфігурацію стільки, скільки йому потрібно, а виділення має зникнути лише тоді, коли зникне останній робітник. ```rust // shared_config.rs use std::rc::Rc; #[derive(Debug)] struct Config { endpoint: String, timeout_ms: u32, } fn main() { // Rc::new moves Config onto the heap with a strong count of 1. let config = Rc::new(Config { endpoint: "https://api.example.com".to_string(), timeout_ms: 5000, }); // Rc::clone only bumps the reference count; it does not deep-copy Config. let worker_a = Rc::clone(&config); let worker_b = Rc::clone(&config); // All three handles point to the same allocation. println!("endpoint: {}", worker_a.endpoint); println!("timeout: {}", worker_b.timeout_ms); // strong_count reports how many owners currently hold the value. println!("owners: {}", Rc::strong_count(&config)); // owners: 3 } ``` Явний виклик `Rc::clone` (замість `config.clone()`) є ідіоматичним: він сигналізує читачам, що операція дешева й лише торкається лічильника. Заковика в тому, що `Rc` видає тільки спільні, незмінні посилання. Він не може змінювати значення, яким ділиться, і його небезпечно надсилати між потоками. [Документація `Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) роз'яснює обидва обмеження. ## `RefCell` і внутрішня змінюваність у Rust Rust зазвичай вимагає, щоб значення або було спільним через багато незмінних посилань, або змінювалося через рівно одне змінне посилання, і перевіряє це під час компіляції. `RefCell` забезпечує внутрішню змінюваність: він дозволяє коду змінювати значення через спільне посилання, переносячи ту саму перевірку на час виконання. `borrow()` повертає спільний охоронець читання; `borrow_mut()` повертає ексклюзивний охоронець запису. Правила ідентичні, але порушення викликають паніку замість помилки компіляції. У поєднанні з `Rc` це дає канонічний однопотоковий шаблон спільного змінюваного стану `Rc>`: кілька власників, які всі можуть оновлювати те саме значення. ```rust // counter.rs use std::rc::Rc; use std::cell::RefCell; // Rc gives shared ownership; RefCell allows mutation through a shared reference. type SharedCounter = Rc>; fn increment(counter: &SharedCounter) { // borrow_mut() hands out an exclusive reference, checked at runtime. *counter.borrow_mut() += 1; } fn main() { let counter: SharedCounter = Rc::new(RefCell::new(0)); let handle_a = Rc::clone(&counter); let handle_b = Rc::clone(&counter); increment(&handle_a); increment(&handle_b); increment(&counter); // borrow() gives a shared read guard; the value is now 3. println!("count = {}", counter.borrow()); // count = 3 } ``` > **RefCell переносить перевірку позичання на час виконання** > > Виклик `borrow_mut()`, поки живий інший охоронець `borrow()` чи `borrow_mut()`, викликає паніку з повідомленням `already borrowed: BorrowMutError`. Гарантії безпеки зберігаються, але логічна помилка, яку компілятор би виявив, стає збоєм під час виконання. Охоронці варто тримати короткоживучими й уникати утримання позичання протягом виклику функції, що може повторно ввійти в той самий `RefCell`. Внутрішня змінюваність — це також місце, де ховаються цикли посилань. Два значення `Rc>`, що вказують одне на одне, назавжди тримають сильний лічильник одне одного вище нуля, тож жодне ніколи не звільняється. Розв'язанням є `Weak`, дескриптор без володіння, який не впливає на сильний лічильник; класичний розбір міститься в [Learn Rust With Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). ## `Arc`: потокобезпечний підрахунок посилань для конкурентності `Arc` (з атомарним підрахунком посилань) — це багатопотоковий родич `Rc`. Публічний API майже ідентичний, але лічильник використовує атомарні операції, тож клонування та знищення `Arc` з кількох потоків одночасно залишається коректним. Саме через цю атомарність `Arc` обирають лише тоді, коли спільне використання перетинає межу потоку: атомарне збільшення відчутно повільніше за звичайне, яке використовує `Rc`. Оскільки `Arc` усе ще видає незмінні посилання, зміна між потоками потребує примітиву синхронізації. `Mutex` — звичний партнер, що дає шаблон `Arc>`, який віддзеркалює `Rc>` для конкурентного коду. Приклад запускає чотири потоки, кожен з яких тисячу разів збільшує спільну суму. ```rust // parallel_sum.rs use std::sync::{Arc, Mutex}; use std::thread; fn main() { // Arc is safe to share across threads; Mutex serializes access to the inner value. let total = Arc::new(Mutex::new(0u64)); let mut handles = Vec::new(); for _ in 0..4 { // Each thread gets its own Arc handle (an atomic count bump). let total = Arc::clone(&total); handles.push(thread::spawn(move || { for _ in 0..1000 { // lock() blocks until the mutex is free, then returns a guard. let mut value = total.lock().unwrap(); *value += 1; } })); } for handle in handles { handle.join().unwrap(); // wait for every thread before reading } println!("total = {}", *total.lock().unwrap()); // total = 4000 } ``` Компілятор сам забезпечує цю межу: `Rc` не є `Send`, тож спроба перемістити його в `thread::spawn` не скомпілюється, підштовхуючи до `Arc`. Звертання до `Arc` в межах одного потоку означає лише плату за атомарність, яка ніколи не використовується. [Документація `Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) докладно описує гарантії впорядкування пам'яті, а `Arc>` лежить в основі значної частини спільного стану в [асинхронному Rust із Tokio](/blog/rust/rust-async-await-tokio-futures-concurrency). ## Box vs Rc vs Arc vs RefCell: коли який використовувати Ці чотири типи поєднуються за двома осями: скільки власників має значення та чи можна його змінювати через спільний дескриптор. Таблиця підсумовує компроміси. | Тип | Володіння | Зміна через спільний дескриптор | Потокобезпечний | Накладні витрати | |------|-----------|----------------------------|-------------|----------| | `Box` | Одне | Ні | Так, якщо `T: Send` | Лише розміщення в купі | | `Rc` | Спільне | Ні | Ні | Неатомарний лічильник | | `Arc` | Спільне | Ні | Так | Атомарний лічильник | | `RefCell` | Одне | Так (перевірка під час виконання) | Ні | Прапорець позичання під час виконання | Практичне дерево рішень коротке. Потрібен один власник у купі: `Box`. Потрібно багато власників в одному потоці: `Rc`. Потрібно багато власників між потоками: `Arc`. Потрібно змінювати спільне значення: внутрішній тип загортають у `RefCell` (один потік) або `Mutex` (багато потоків). Рекомендації Effective Rust щодо [типів посилань і вказівників](https://www.lurklurk.org/effective-rust/) доходять до тієї самої багатошаровості. Найкраще починати з найменш потужного варіанта й додавати можливості лише тоді, коли цього вимагає компілятор. ## Питання зі співбесід про розумні вказівники в Rust Розумні вказівники — надійна тема співбесід, бо вони перевіряють, чи розуміє кандидат володіння, а не лише синтаксис. Наведені нижче питання трапляються часто, а більше їх зібрано в [модулі співбесід про розумні вказівники в Rust](/technologies/rust/interview-questions/smart-pointers). **У чому різниця між `Rc` та `Arc`?** Обидва забезпечують спільне володіння через підрахунок посилань. `Rc` використовує неатомарний лічильник і обмежений одним потоком; `Arc` використовує атомарні операції й може бути спільним між потоками за невелику ціну під час виконання. Компілятор забезпечує цей поділ: `Rc` не є ні `Send`, ні `Sync`, тож не може перетнути межу потоку. **Навіщо поєднувати `Rc` з `RefCell`?** `Rc` надає спільне володіння, але лише незмінний доступ. `RefCell` додає внутрішню змінюваність, дозволяючи власникам змінювати значення через спільне посилання з перевіркою позичання під час виконання. Разом `Rc>` є ідіоматичним однопотоковим будівельним блоком спільного змінюваного стану. **Як підрахунок посилань може спричинити витік пам'яті в Rust?** Два значення `Rc` (або `Arc`), що посилаються одне на одне, утворюють цикл, чиї сильні лічильники ніколи не досягають нуля, тож виділення ніколи не звільняється. Розрив циклу за допомогою `Weak`, який тримає посилання без володіння, відновлює правильне очищення. **Коли `Box` кращий за `Rc`?** Щоразу, коли достатньо одного власника. `Box` не має накладних витрат на підрахунок посилань, тож це типовий вибір для розміщення в купі, рекурсивних типів і трейт-об'єктів. До `Rc` варто вдаватися лише тоді, коли потрібне справжнє спільне володіння. ## Висновок Розумні вказівники в Rust перетворюють володіння з обмеження на набір поєднуваних будівельних блоків. Вибір між ними випливає безпосередньо з форми задачі: - `Box` варто обирати, коли значенню потрібен один власник у купі, рекурсивному типу потрібен фіксований розмір або функція повертає трейт-об'єкт. - `Rc` застосовують для кількох власників в одному потоці й переходять на `Arc` у момент, коли володіння перетинає межу потоку. - `RefCell` додають для внутрішньої змінюваності в однопотоковому коді, а `Mutex` — для конкурентного еквівалента, тримаючи охоронці позичання й блокування короткоживучими. - Поєднувати їх слід свідомо: `Rc>` для спільного змінюваного стану в одному потоці, `Arc>` між потоками. - Слід стежити за циклами посилань між лічильними вказівниками й розривати їх за допомогою `Weak`, щоб уникнути витоків. - За замовчуванням варто обирати найменш потужний тип і дозволяти компілятору підказати, коли потрібно більше можливостей. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/rust/rust-smart-pointers-box-rc-arc-refcell