# Rc в Rust: Разделённое владение, циклические ссылки и Weak<T>

> Узнайте, как работает Rc в Rust: счётчик ссылок, разделяемое владение между структурами, связка с RefCell, проблема циклических ссылок (Rc cycles) и её решение через Weak<T>.

- **URL:** https://eagle2000.space/rust/rust-rc/
- **Date:** 2025-06-03

- **Tags:** rust, dev, rc, weak, refcell, middle

---
## Введение

Представьте ситуацию: у вас есть данные в куче (`heap`), и вы хотите, чтобы несколько независимых объектов могли одновременно ими пользоваться. В обычных языках программирования ссылки раздаются направо и налево, но в Rust строгая система владения требует четкого понимания: кто является единственным владельцем ресурса?

Если владельцев должно быть несколько одновременно (в пределах одного потока), на сцену выходит умный указатель **`Rc<T>`** (Reference Counted).

---

## 1. Что такое Rc и как работает strong_count

`Rc<T>` — это управляющий указатель с подсчетом ссылок для **однопоточных** сценариев.

Каждый раз, когда вы делаете `Rc::clone(&a)`, вы не копируете сами данные внутри кучи. Вместо этого вы просто увеличиваете служебный счетчик сильных ссылок — `strong_count` — на 1. А когда очередная переменная выходит из области видимости, `drop()` уменьшает `strong_count` на 1.

Как только `strong_count` становится равен 0, память кучи автоматически очищается!

> [!NOTE]
> Почему бы не использовать обычную ссылку `&T`?
> `&T` — это временное заимствование. Ссылка жестко ограничена временем жизни (`lifetimes`), её нельзя просто так сохранить в структуре без явной аннотации `'a`. `Rc<T>` решает эту проблему, давая независимое разделяемое владение.

### Пример разделения данных между структурами

Представьте, что у нас есть описание города **City**, которое одновременно используется в справочнике туриста и в отчете транспортной компании:


### 💻 Пример c городами и историей


#### Шаг 1

```rust
// ?compile_fail
#[derive(Debug)]
struct City {
    name: String,
    history: String,
}

#[derive(Debug)]
struct CitySummary {
    headline: String,
    description: String,
}

fn main() {
    let history = String::from("Город основан в 1574 году");

    let city = City {
        name: String::from("Уфа"),
        history: history,
    };

    let summary = CitySummary {
        headline: String::from("Уфа"),
        description: history, // ❌ перемещено из-под city
    };

    println!("{:?}, {:?}", city, summary);
}
```

**1. Ошибка: попытка поделиться строкой напрямую между структурами**  
- Мы либо копируем строку, либо нарушаем правила владения, переместив значение. ⚠️ _**Данные не могут быть одновременно в двух структурах**_.


#### Шаг 2

```rust
use std::rc::Rc;

#[derive(Debug)]
struct City {
    name: Rc<String>,
    history: Rc<String>,
}

#[derive(Debug)]
struct CitySummary {
    headline: Rc<String>,
    description: Rc<String>,
}

fn main() {
    let name = Rc::new(String::from("Уфа"));
    let history = Rc::new(String::from("Основан в 1574 году"));

    let city = City {
        name: Rc::clone(&name),
        history: Rc::clone(&history),
    };

    let summary = CitySummary {
        headline: Rc::clone(&name),
        description: Rc::clone(&history),
    };

    println!("Город: {:?} Сводка: {:?}", city, summary);
    println!("Ссылок на описание: {}", Rc::strong_count(&history));
}
```

**2. Решение: использование Rc для разделения строк**
- С Rc<T> можно безопасно использовать одни и те же данные в нескольких структурах. Владение разделено, а данные освобождаются когда последний владелец исчезает.


---

## 2. Связка Rc + RefCell для внутренней мутабельности

Сам по себе `Rc<T>` дает только **иммутабельный** доступ к имеющимся данным. Если вы попробуете изменить значение внутри `Rc`, компилятор Rust выдаст ошибку.

Что делать, если несколько объектов должны не только читать, но и динамически менять общее состояние? 
Ответ — обернуть данные в **`RefCell<T>`**, получив комбинацию `Rc<RefCell<T>>`.

### Ошибки и паника в рантайме

`RefCell` переносит проверки правил заимствования (borrow checker) с этапа компиляции на этап выполнения программы (runtime). 
Если вы случайно попытаетесь взять две изменяемые ссылки `borrow_mut()` одновременно на один и тот же `RefCell`, программа аварийно завершится с паникой!


### 💻 Примеры работы с RefCell


#### Шаг 1

```rust
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let users = Rc::new(RefCell::new(vec!["Алиса".to_string()]));

    let session1 = Rc::clone(&users);
    let session2 = Rc::clone(&users);

    session1.borrow_mut().push("Макс".to_string());
    session2.borrow_mut().push("Петр".to_string());

    println!("Онлайн: {:?}", users.borrow());
}
```

**1. Общий список пользователей в чате**
- Мы моделируем общее состояние `users`, доступное из разных частей приложения. RefCell позволяет менять данные при множественных владельцах.


#### Шаг 2

```rust
// ?should_panic
#[allow(unused_variables)]
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

    let _first = shared.borrow_mut();
    let _second = shared.borrow_mut(); // ❌ паника: уже есть активный borrow_mut
}
```

**2. Ошибка: двойной borrow_mut при вложенных вызовах**
- `RefCell` проверяет заимствование во время выполнения. Нельзя взять borrow_mut дважды одновременно.


#### Шаг 3

```rust
#[allow(unused_variables)]
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
    let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

    {
        let mut data = shared.borrow_mut();
        data.push(4);
    }

    let again = shared.borrow_mut();
    println!("{:?}", again);
}
```

**3. Исправление: ограничиваем область действия borrow**
- Вложите `borrow_mut` в отдельный блок — это освободит заимствование до следующего вызова. ✅


---

## 3. Проблема циклических ссылок (Rc cycles)

Хотя `Rc` в связке с `RefCell` дает огромную гибкость, здесь подстерегает опасная коварность: **утечка памяти из-за циклических ссылок**.

Если структура А содержит `Rc` узел на структуру Б, а структура Б содержит `Rc` узел обратно на структуру А, мы получаем замкнутый цикл:

1. Узел А имеет `strong_count == 1` (из-за ссылки от Б).
2. Узел Б имеет `strong_count == 1` (из-за ссылки от А).
3. Когда внешние переменные `a` и `b` выходят из функции, счетчики у обоих объектов уменьшаются лишь до 1, но **никогда не становятся равны 0**!

В результате деструктор `Drop` не вызовется ни у одного из узлов, и память в куче останется висеть «навсегда».

---

## 4. Решение через Weak<T> (downgrade и upgrade)

Чтобы разорвать замкнутый круг циклических ссылок, Rust предоставляет ослабленные ссылки — **`Weak<T>`**.

* **Сильная ссылка (`Rc<T>`)** — вы выражаете прямое *владение* объектом. Объект существует до тех пор, пока `strong_count > 0`.
* **Слабая ссылка (`Weak<T>`)** — вы выражаете *наблюдение* за объектом без владения. Создается вызовом `Rc::downgrade(&rc)`.

`Weak<T>` увеличивает служебный счетчик `weak_count`, но **не влияет на `strong_count`**. Это означает, что главный объект в куче спокойно очистится при обнулении `strong_count`, даже если на него до сих пор смотрят слабые ссылки!

### Как работать со ссылками Weak?

Так как объект, на который указывает `Weak<T>`, мог быть уже очищен в памяти, вы не можете разыменовать `Weak` напрямую. Для этого используется метод **`.upgrade()`**:

```rust
let weak_ref: Weak<Gadget> = Rc::downgrade(&gadget);

// Пытаемся временно превратить слабую ссылку обратно в сильную:
if let Some(strong_ref) = weak_ref.upgrade() {
    println!("Объект еще жив: {}", strong_ref.id);
} else {
    println!("Объект уже деаллоцирован!");
}
```

Давайте посмотрим на проблему циклов и их решение через `Weak` на интерактивных слайдах:


### 💻 Циклы ссылок и безопасный Weak


#### Шаг 1

```rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Default)]
struct Node {
    name: String,
    next: Option<Rc<RefCell<Node>>>,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("Деструктор узла '{}' вызван!", self.name);
    }
}

fn main() {
    println!("--- Демонстрация сильных ссылок ---");
    let first = Rc::new(RefCell::new(Node {
        name: "Узел А".to_string(),
        next: None,
    }));
    
    let second = Rc::new(RefCell::new(Node {
        name: "Узел Б".to_string(),
        next: None,
    }));

    println!("Сильные ссылки 'first': {}", Rc::strong_count(&first));
    println!("Сильные ссылки 'second': {}", Rc::strong_count(&second));
    
    // Связываем узел А -> узел Б
    first.borrow_mut().next = Some(Rc::clone(&second));
    println!("После связывания 'second' strong_count: {}", Rc::strong_count(&second));
}
```

**Подсчет сильных ссылок Rc**
- `Rc::strong_count(&a)` позволяет узнать количество владельцев объекта в куче.
- Каждый `Rc::clone` увеличивает `strong_count` на 1, а при выходе переменной из scope — уменьшает на 1.
- Если узел А владеет узлом Б через `Rc`, счетчик ссылок Б становится равен 2.


#### Шаг 2

```rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Gadget {
    id: String,
    owner: Option<Weak<Owner>>, // Ослабленная ссылка на владельца предотвращает циклический вызов Drop!
}

struct Owner {
    name: String,
    gadgets: RefCell<Vec<Rc<RefCell<Gadget>>>>,
}

impl Drop for Gadget {
    fn drop(&mut self) {
        println!("Устройство '{}' удалено из памяти", self.id);
    }
}

impl Drop for Owner {
    fn drop(&mut self) {
        println!("Владелец '{}' удален из памяти", self.name);
    }
}

fn main() {
    println!("--- Безопасная связь через Weak ---");
    
    let owner = Rc::new(Owner {
        name: "Алексей".to_string(),
        gadgets: RefCell::new(Vec::new()),
    });

    let gadget = Rc::new(RefCell::new(Gadget {
        id: "Смартфон".to_string(),
        owner: Some(Rc::downgrade(&owner)), // Создаем ослабленную ссылку Weak<Owner>
    }));

    owner.gadgets.borrow_mut().push(Rc::clone(&gadget));

    println!("Сильные ссылки владельца: {}", Rc::strong_count(&owner));
    println!("Слабые ссылки владельца: {}", Rc::weak_count(&owner));

    // Для обращения к владельцу из устройства используем upgrade():
    if let Some(ref weak_owner) = gadget.borrow().owner {
        if let Some(upgraded_owner) = weak_owner.upgrade() {
            println!("Устройство принадлежит владельцу: {}", upgraded_owner.name);
        }
    }
}
```

**Решение циклов через Weak ссылки (downgrade и upgrade)**
- `Rc::downgrade(&rc)` создает слабую ссылку `Weak<T>`, увеличивая `weak_count`, но НЕ `strong_count`.
- `Weak<T>` не препятствует деаллокации объекта, когда все сильные ссылки выходят из области видимости.
- Для доступа к данным `Weak::upgrade()` возвращает `Option<Rc<T>>`. Если объект уже удален, вернется `None`.


---

## Где использовать Rc и Weak

- **Деревья и Графы:** Узлы хранят сильные ссылки `Rc` на дочерние элементы, а дочерние элементы хранят слабые ссылки `Weak` на родительский узел (Parent pointers).
- **Кэширование:** Кэш хранит `Weak` ссылки на объекты. Если объект больше никому в программе не нужен, он автоматически выгружается из памяти.
- **Событийно-ориентированные системы:** Подписчики на события хранят `Weak` ссылки на слушателей.

---

## Проверь свои знания!

Пройдите короткий тест, чтобы закрепить знания по работе с `Rc`, `RefCell`, цикличным ссылкам и типажу `Weak<T>`.


### 📝 Разделяемое владение и Weak


**1. Что происходит со счетчиками ссылок при вызове `Rc::downgrade(&rc_ptr)`?**

- [ ] strong_count увеличивается на 1, а weak_count не меняется
- [ ] strong_count не меняется, а weak_count увеличивается на 1
- [ ] strong_count уменьшается на 1, а объект перемещается на стек

**2. К чему приводит создание взаимных сильных ссылок `Rc` между двумя объектами?**

- [ ] К ошибке компиляции borrow checker
- [ ] К циклу ссылок (Rc cycle) и скрытой утечке памяти, так как strong_count обоих объектов никогда не станет равен 0
- [ ] К автоматической конвертации типа в Arc

**3. Как получить безопасный доступ к данным, находящимся под слабой ссылкой `Weak<T>`?**

- [ ] Разыменовать ее напрямую оператором `*weak_ref`
- [ ] Вызвать метод `weak_ref.upgrade()`, который возвращает `Option<Rc<T>>`
- [ ] Принудительно вызвать метод `.unwrap()`

**4. Когда освобождается память под объект `T`, обернутый в `Rc<T>`?**

- [ ] Только когда и strong_count, и weak_count одновременно станут равны 0
- [ ] Как только `strong_count` становится равен 0 (даже если `weak_count` больше 0)
- [ ] При закрытии приложения

---

{{< alert "fire" >}}
Не забывайте, что `Rc` и `Weak` предназначены для однопоточного кода. ❗️
Если вам нужно делиться данными между несколькими параллельными потоками — для этого используется потокобезопасная пара `Arc<T>` и **`std::sync::Weak<T>`**, подробный разбор которой вы найдете в [отдельной статье про Arc](../rust-arc/)!
{{< /alert >}}
