hardware
This commit is contained in:
parent
f2b07c9f30
commit
17f37947f3
8 changed files with 485 additions and 16 deletions
24
10-linux/00-utils/sops/base workflow with sops.md
Normal file
24
10-linux/00-utils/sops/base workflow with sops.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
```
|
||||||
|
age-keygen -y ~/.config/sops/age/keys.txt
|
||||||
|
```
|
||||||
|
Вот это выведет публичный ключ для вставки в .sops
|
||||||
|
|
||||||
|
пример содержимого `.sops`
|
||||||
|
|
||||||
|
```
|
||||||
|
# .sops.yaml
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets.yaml
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- age12345...6789
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
затем
|
||||||
|
|
||||||
|
`sops secrets.yaml`
|
||||||
|
|
||||||
|
и вуаля - там уже образец примера файла будет.
|
||||||
|
|
||||||
|
|
||||||
2
10-linux/00-utils/ssh/Untitled.md
Normal file
2
10-linux/00-utils/ssh/Untitled.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
скопировать что нибудь
|
||||||
|
`rsync -avz ./themes/ user@remote_host:~/.config/helix/themes/`
|
||||||
File diff suppressed because one or more lines are too long
88
10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md
Normal file
88
10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
Давай разберем эту иерархию от самого быстрого и "близкого" к мозгам процессора до более объемного. Это можно представить как пирамиду: чем ближе к вершине, тем меньше памяти, но доступ к ней мгновенный.
|
||||||
|
|
||||||
|
### 1. Регистры (Registers)
|
||||||
|
Это самая быстрая память, которая находится прямо в ядре процессора.
|
||||||
|
* **Аналогия:** Это твои **руки**. Данные здесь — это то, что ты держишь прямо сейчас.
|
||||||
|
* **Объем:** Крошечный. Регистров всего несколько десятков штук (например, 16 регистров общего назначения в x86-64).
|
||||||
|
* **Размер:** В архитектуре x86-64 (64-битной) один регистр вмещает ровно **64 бита** (8 байт).
|
||||||
|
* **Скорость:** **0 тактов** (мгновенно). Арифметические операции (сложение, умножение) процессор может делать *только* над значениями, которые уже лежат в регистрах.
|
||||||
|
* **SIMD-регистры:** Это особые широкие регистры (128, 256 или 512 бит), в которые можно положить сразу пачку чисел (например, четыре `float32`) и обработать их одной инструкцией. Именно их использует Rust-компилятор для векторизации.
|
||||||
|
|
||||||
|
### 2. Кэш процессора (CPU Cache)
|
||||||
|
Это сверхоперативная память типа **SRAM** (Static RAM), расположенная на кристалле процессора. Она нужна, чтобы процессор не простаивал, ожидая данные из медленной оперативной памяти (RAM).
|
||||||
|
* **Аналогия:** Это **верстак** или рабочий стол. Данные здесь лежат "под рукой".
|
||||||
|
* **Уровни (L1, L2, L3):**
|
||||||
|
* **L1 (Level 1):** Самый маленький (например, 32 КБ для команд + 32 КБ для данных на ядро), но самый быстрый. Доступ занимает ~3-4 такта.
|
||||||
|
* **L2:** Побольше (256 КБ - 1 МБ на ядро), чуть медленнее (~10-12 тактов).
|
||||||
|
* **L3:** Общий для всех ядер (десятки МБ), еще медленнее (~40-50 тактов), но все равно намного быстрее RAM.
|
||||||
|
|
||||||
|
### 3. Кэш-линия (Cache Line)
|
||||||
|
Это **минимальная единица обмена** данными между RAM и кэшем.
|
||||||
|
* **Размер:** Стандарт индустрии — **64 байта**.
|
||||||
|
* **Суть:** Процессор не умеет читать из памяти "один байт". Если твой код просит переменную типа `u8` (1 байт), процессор всё равно загрузит из RAM целую линию (64 байта), в которой лежит этот байт, и поместит её в кэш.
|
||||||
|
* **Почему это важно для структур данных:**
|
||||||
|
* Если у тебя **массив** (или `Vec`), данные лежат плотно. Загрузив одну кэш-линию, процессор получает сразу 16 чисел `i32` (так как 16 * 4 байта = 64). Следующие 15 обращений к массиву будут мгновенными (взяты из L1 кэша).
|
||||||
|
* Если у тебя **связный список**, каждый элемент может лежать в памяти далеко друг от друга. Чтобы прочитать 16 элементов, процессору придется 16 раз сходить в медленную RAM, загружая 16 разных кэш-линий, из которых полезными будут только по 4-8 байт. Это называется **Cache Miss** (промах кэша), и это убивает производительность.
|
||||||
|
|
||||||
|
### Итог
|
||||||
|
1. Процессор загружает данные из RAM **кэш-линиями** (по 64 байта) в **кэш**.
|
||||||
|
2. Из кэша данные попадают в **регистры** (по 8 байт).
|
||||||
|
3. АЛУ (арифметико-логическое устройство) выполняет операции над регистрами.
|
||||||
|
|
||||||
|
В B-Tree мы храним ключи массивом, поэтому одна загрузка кэш-линии дает нам сразу много ключей для сравнения, и мы максимально эффективно используем эту механику.
|
||||||
|
|
||||||
|
[1](https://ru.wikipedia.org/wiki/%D0%9A%D1%8D%D1%88_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0%B0)
|
||||||
|
[2](https://market.marvel.ru/blog/komplektuyushchie-i-optsii/kesh-pamyat-protsessora/)
|
||||||
|
[3](https://club.dns-shop.ru/blog/t-100-protsessoryi/37338-chto-takoe-kesh-v-protsessore-i-zachem-on-nujen/)
|
||||||
|
[4](https://man-made.ru/articles/chto-takoe-kesh-pamyat-protsessora/)
|
||||||
|
[5](https://otus.ru/journal/kesh-processora-opisanie-urovni-osobennosti/)
|
||||||
|
[6](https://skyeng.ru/magazine/wiki/it-industriya/chto-takoe-kesh-pamiat/)
|
||||||
|
[7](https://overclockers.ru/blog/Hardware_inc/show/240934/Kesh-processora-chto-eto-takoe-i-kak-on-zastavlyaet-vash-komp-juter-rabotat-bystree-Chast-1)
|
||||||
|
[8](https://compress.ru/article.aspx?id=23541)
|
||||||
|
[9](https://habr.com/ru/companies/vdsina/articles/515660/)
|
||||||
|
[10](https://www.youtube.com/watch?v=7n_8cOBpQrg)
|
||||||
|
|
||||||
|
|
||||||
|
Помимо иерархии памяти (Регистры → Кэш → RAM), для написания высокопроизводительного кода (особенно на Rust/C++) критически важно понимать еще три концепции. Они объясняют, почему "наивный" код часто работает медленнее, чем мог бы.
|
||||||
|
|
||||||
|
### 1. Конвейер (Pipeline) и Предсказание ветвлений (Branch Prediction)
|
||||||
|
|
||||||
|
Современный процессор — это фабрика. Он не делает одну инструкцию за раз; он выполняет их параллельно на разных стадиях (чтение, декодирование, выполнение, запись).
|
||||||
|
* **Проблема:** Когда процессор видит условный переход `if` (ветвление), он не знает, куда пойдет код дальше, пока условие не вычислится.
|
||||||
|
* **Решение:** Процессор **угадывает** (Branch Prediction). Он начинает выполнять ветку `true` заранее (спекулятивное выполнение).
|
||||||
|
* **Цена ошибки:** Если процессор не угадал (Branch Misprediction), он должен выбросить все вычисления и начать заново с правильной ветки. Это огромная потеря времени (10-20 тактов).
|
||||||
|
* **Для разработчика:**
|
||||||
|
* Отсортированные массивы обрабатываются быстрее (паттерн ветвлений предсказуем: TTTTFFFF).
|
||||||
|
* В B-Tree поиск внутри узла часто делают линейным (без ветвлений, через SIMD) или оптимизированным бинарным, чтобы не сбивать предсказатель.
|
||||||
|
|
||||||
|
### 2. Виртуальная память и TLB (Translation Lookaside Buffer)
|
||||||
|
|
||||||
|
Адреса, которые видит твоя программа (например, указатель `0x7ff...`), — ненастоящие. Это **виртуальные адреса**. Процессор должен каждый раз переводить их в **физические адреса** RAM.
|
||||||
|
* **TLB:** Это специальный маленький "кэш для адресов". Он помнит последние переводы страниц памяти.
|
||||||
|
* **TLB Miss:** Если ты прыгаешь по памяти слишком хаотично (например, в огромном графе или HashMap), TLB не находит перевод, и процессору приходится лезть в "таблицы страниц" (Page Walk), что очень дорого.
|
||||||
|
* **Почему это важно:** Локальность данных (B-Tree, массивы) спасает не только кэш данных (L1/L2), но и TLB. Меньше прыжков по страницам — быстрее работа.
|
||||||
|
|
||||||
|
### 3. Суперскалярность и Зависимость по данным (Data Dependency)
|
||||||
|
|
||||||
|
Ядро процессора имеет несколько исполнительных блоков (ALU). Оно может выполнить, например, 4 сложения за один такт, если они независимы.
|
||||||
|
* **Плохо:** `a = b + 1; c = a + 2;` (Второе действие ждет первого).
|
||||||
|
* **Хорошо:** `a = b + 1; c = d + 2;` (Процессор сделает это одновременно).
|
||||||
|
* **Для разработчика:** Иногда развертывание циклов (loop unrolling) или обработка данных независимыми блоками дает ускорение именно за счет загрузки всех ALU ядра.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### ИТОГ: Ключевые элементы процессора для программиста
|
||||||
|
|
||||||
|
Чтобы писать эффективный код (особенно структуры данных), нужно держать в голове эту "карту железа":
|
||||||
|
|
||||||
|
| Элемент | Что это | Почему важно понимать |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Регистры** | Рабочая зона ядра (мгновенно) | Данные должны быть здесь для вычислений. Компилятор пытается держать "горячие" переменные тут. |
|
||||||
|
| **Кэш-линия** | 64 байта данных (транспорт) | Читаем память блоками. **Массивы (Vec) — короли**, связные списки — зло. B-Tree выигрывает за счет плотности данных. |
|
||||||
|
| **Кэш (L1/L2/L3)** | Быстрая память (на кристалле) | **Cache Miss** — главный враг производительности. Чем компактнее твои данные, тем больше их влезет в кэш. |
|
||||||
|
| **Branch Predictor** | Предсказатель `if`'ов | Непредсказуемые условия (рандомные `if`) сбрасывают конвейер. Иногда лучше вычислить лишнее, чем ветвиться. |
|
||||||
|
| **SIMD** | Векторные инструкции | Обработка 4-8 чисел одной командой. Rust делает это сам, если ты используешь итераторы и простые циклы. |
|
||||||
|
| **TLB** | Кэш адресов памяти | Хаотичные прыжки по памяти (Pointer Chasing) забивают не только кэш данных, но и TLB. |
|
||||||
|
|
||||||
|
**Главный вывод для Rust-разработчика:**
|
||||||
|
Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе.
|
||||||
85
20-dev/00-rust/rustlings/Untitled.md
Normal file
85
20-dev/00-rust/rustlings/Untitled.md
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют **свертку** (fold) или, в более простых случаях, готовые методы агрегации вроде `product`.
|
||||||
|
|
||||||
|
В Rust у итераторов есть метод `product()`, который делает ровно то, что нужно для факториала.
|
||||||
|
|
||||||
|
### Решение 1: Самое идиоматичное (Iterators + product)
|
||||||
|
|
||||||
|
Мы создаем диапазон от `1` до `num` (включительно) и перемножаем все числа.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn factorial(num: u64) -> u64 {
|
||||||
|
(1..=num).product()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Почему это работает:**
|
||||||
|
1. `(1..=num)` создает итератор, генерирующий числа `1, 2, 3, ..., num`.
|
||||||
|
* Если `num` равно `0`, диапазон `1..=0` пуст.
|
||||||
|
2. `.product()` проходит по итератору, умножая аккумулятор на каждый элемент.
|
||||||
|
* Для пустого итератора (факториал 0) `product()` возвращает мультипликативную единицу (`1`), что математически верно: $0! = 1$.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Решение 2: Более явное (Fold)
|
||||||
|
|
||||||
|
Если бы метода `product()` не существовало, мы бы использовали `fold`. Это классическая "reduce" операция из функционального программирования.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn factorial(num: u64) -> u64 {
|
||||||
|
(1..=num).fold(1, |acc, x| acc * x)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Разбор:**
|
||||||
|
* `1` (первый аргумент) — начальное значение аккумулятора.
|
||||||
|
* `|acc, x| acc * x` — замыкание, которое берет текущий аккумулятор (`acc`) и следующий элемент (`x`), и возвращает новое значение аккумулятора.
|
||||||
|
* Для `num = 4`:
|
||||||
|
1. `acc = 1` (начальное), `x = 1` -> `1 * 1 = 1`
|
||||||
|
2. `acc = 1`, `x = 2` -> `1 * 2 = 2`
|
||||||
|
3. `acc = 2`, `x = 3` -> `2 * 3 = 6`
|
||||||
|
4. `acc = 6`, `x = 4` -> `6 * 4 = 24`
|
||||||
|
|
||||||
|
**Итог:**
|
||||||
|
Используй `(1..=num).product()` — это самый "ржавый" (Rustacean) и читаемый способ. Он выполняет все твои условия:
|
||||||
|
* Нет явного `return` (это выражение).
|
||||||
|
* Нет циклов `for/while` (спрятаны внутри итератора).
|
||||||
|
* Нет мутабельных переменных.
|
||||||
|
* Нет рекурсии.
|
||||||
|
|
||||||
|
|
||||||
|
### 1. "Джентльменский набор" итераторов
|
||||||
|
Помимо уже знакомых тебе `map`, `filter`, `fold` и `flat_map`, вот что ты будешь использовать в 90% случаев:
|
||||||
|
|
||||||
|
* **`collect()`** — ты уже знаешь, это "материализатор". Превращает поток обратно в коллекцию (`Vec`, `HashMap`, `String`...).
|
||||||
|
* **`find(|x| ...)`** — ищет **первый** элемент, удовлетворяющий условию. Возвращает `Option<T>`.
|
||||||
|
* **`any(|x| ...)` / `all(|x| ...)`** — возвращают `bool`. `any` проверяет, есть ли *хотя бы один*, `all` — что *все* соответствуют.
|
||||||
|
* **`enumerate()`** — добавляет индекс к элементу. Вместо `x` получаешь `(index, x)`.
|
||||||
|
```rust
|
||||||
|
// Вывести строки с номерами
|
||||||
|
lines.iter().enumerate().for_each(|(i, line)| println!("{}: {}", i, line));
|
||||||
|
```
|
||||||
|
* **`zip(other_iter)`** — "сшивает" два итератора в один поток пар `(a, b)`.
|
||||||
|
* **`take(n)` / `skip(n)`** — берет первые `n` или пропускает первые `n` элементов.
|
||||||
|
* **`chain(other_iter)`** — склеивает два итератора: "сначала всё из первого, потом всё из второго".
|
||||||
|
* **`inspect(|x| ...)`** — бесценно для отладки! Позволяет "подсмотреть" значение в середине цепочки, не меняя его (обычно там делают `println!`).
|
||||||
|
|
||||||
|
### 2. Вся ли функциональщина в Rust — это итераторы?
|
||||||
|
И да, и нет.
|
||||||
|
* **Да**, итераторы — это главный локомотив функционального стиля в Rust. Это самый мощный и оптимизированный инструмент для обработки данных.
|
||||||
|
* **Нет**, ФП в Rust шире. Оно включает:
|
||||||
|
* **Enum + Pattern Matching** (алгебраические типы данных) — это основа логики, замена классам и полиморфизму.
|
||||||
|
* **Immutability** (неизменяемость по умолчанию).
|
||||||
|
* **Closures** (замыкания) — функции высшего порядка, которые можно передавать и возвращать.
|
||||||
|
|
||||||
|
Но именно цепочки итераторов делают код "визуально функциональным".
|
||||||
|
|
||||||
|
### 3. Каждую ли задачу можно решить функционально?
|
||||||
|
**Теоретически — да.** Любой цикл можно переписать через рекурсию или `fold`.
|
||||||
|
**Практически — не стоит.**
|
||||||
|
|
||||||
|
Rust — прагматичный язык. Иногда **императивный код лучше**:
|
||||||
|
1. **Сложная мутация состояния**: Если тебе нужно обновлять 5 разных переменных в зависимости от сложной логики на каждом шаге, `fold` превратится в ад с кортежем из 5 элементов. Обычный `for` будет чище.
|
||||||
|
2. **Ранний выход из вложенных циклов**: Выйти из тройного вложенного цикла через `break 'label` проще, чем писать цепочку итераторов, которая умеет прерываться.
|
||||||
|
3. **Графы и произвольный доступ**: Итераторы хороши для *последовательностей*. Если ты прыгаешь по индексам массива туда-сюда (`i`, `i+5`, `i/2`), функциональный стиль будет выглядеть как натягивание совы на глобус.
|
||||||
|
|
||||||
|
**Совет:** Используй итераторы для *трансформации потоков данных* (фильтрация, маппинг, поиск, агрегация). Используй циклы для *сложного управления потоком выполнения* или когда состояние слишком запутанное. В Rust нормально сочетать оба подхода.
|
||||||
159
20-dev/00-rust/smart pointers/Rc.md
Normal file
159
20-dev/00-rust/smart pointers/Rc.md
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
`Rc<T>` (Reference Counting) используется для ситуаций **разделяемого владения** (shared ownership), когда данные должны жить до тех пор, пока жив хотя бы один их "владелец".
|
||||||
|
|
||||||
|
### Зачем это нужно?
|
||||||
|
Основная причина: **неизвестность времени жизни в compile-time**.
|
||||||
|
В обычной модели владения Rust (`Box<T>`) у данных может быть только *один* владелец. Но в реальных структурах данных (графы, деревья с обратными ссылками, UI-компоненты) часто бывает, что "родителей" много, и мы не знаем заранее, кто из них удалится последним.
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
Узел графа. На него ссылаются 5 других узлов. Если владелец только один, то удаление этого "главного" узла сломает все остальные 4 ссылки (сделает их невалидными). `Rc` решает это: узел жив, пока на него есть хотя бы одна ссылка.
|
||||||
|
|
||||||
|
### Основные характеристики
|
||||||
|
1. **Shared Ownership:** Позволяет нескольким частям программы владеть одними данными.
|
||||||
|
2. **Immutable:** `Rc<T>` позволяет получить только **иммутабельную** (неизменяемую) ссылку `&T` на данные.[2]
|
||||||
|
* *Почему?* Если бы `Rc` давал `&mut T`, это нарушило бы правила заимствования (множество мутабельных ссылок на одни данные = data race).
|
||||||
|
* *Как изменять?* Для изменяемости внутри `Rc` используется паттерн **Interior Mutability** (обычно в связке `Rc<RefCell<T>>`).
|
||||||
|
3. **Single-threaded:** `Rc` не потокобезопасен. Счетчик ссылок обновляется обычными арифметическими операциями (быстро), а не атомарными. Для многопоточности есть `Arc<T>` (Atomic Reference Counting).[6][2]
|
||||||
|
|
||||||
|
### Как это работает (под капотом)
|
||||||
|
`Rc::new(v)` аллоцирует в куче структуру, содержащую:
|
||||||
|
* Само значение `v`.
|
||||||
|
* `strong_count`: счетчик "сильных" ссылок (владельцев).
|
||||||
|
* `weak_count`: счетчик "слабых" ссылок (для предотвращения циклов).
|
||||||
|
|
||||||
|
Каждый `Rc::clone(&rc)` не копирует данные, а просто инкрементирует счетчик `strong_count`.
|
||||||
|
Когда `Rc` выходит из области видимости (`drop`), счетчик декрементируется.
|
||||||
|
Когда `strong_count == 0`, данные удаляются из памяти (`free`).
|
||||||
|
|
||||||
|
### Итог
|
||||||
|
Используйте `Rc`, когда данные нужны в нескольких местах, и вы не можете построить иерархию, где один владелец живет дольше всех остальных. Но помните, что `Rc` дает только чтение. Для записи нужна обертка `RefCell`.
|
||||||
|
|
||||||
|
[1](https://labex.io/ru/tutorials/rc-t-the-reference-counted-smart-pointer-100434)
|
||||||
|
[2](https://doc.rust-lang.org/book/ch15-04-rc.html)
|
||||||
|
[3](https://www.reddit.com/r/rust/comments/1n65om2/is_stdrcrc_identical_to_references_without/)
|
||||||
|
[4](https://habr.com/ru/companies/bitrix/articles/878912/comments/)
|
||||||
|
[5](https://rust-book.cs.brown.edu/ch15-04-rc.html)
|
||||||
|
[6](https://doc.rust-lang.org/std/rc/struct.Rc.html)
|
||||||
|
[7](https://my-js.org/docs/guide/rust)
|
||||||
|
[8](https://notes.kodekloud.com/docs/Rust-Programming/Advanced-Rust-Concepts/Rc-Reference-Counting-and-Shared-Ownership)
|
||||||
|
[9](https://labex.io/ru/tutorials/exploring-rust-s-reference-counting-mechanism-99263)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Реализация B-дерева на `Rc` — это классический пример того, где приходится использовать паттерн **Interior Mutability** (внутренняя изменяемость).
|
||||||
|
|
||||||
|
Почему? Потому что `Rc<T>` дает нам **неизменяемую** ссылку на данные. А в B-дереве нам нужно постоянно менять содержимое узлов (добавлять ключи, перекидывать детей). Чтобы "обойти" неизменяемость `Rc`, мы кладем данные внутрь `RefCell`.
|
||||||
|
|
||||||
|
Получается "бутерброд": `Rc<RefCell<Node>>`.
|
||||||
|
|
||||||
|
Вот упрощенная реализация структуры и поиска (полная реализация со сплитами и балансировкой заняла бы сотни строк, поэтому я покажу суть связывания через `Rc`):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
// 1. Опишем узел.
|
||||||
|
// B-дерево состоит из ключей и детей.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Node<T> {
|
||||||
|
keys: Vec<T>,
|
||||||
|
// Дети хранятся через Rc, чтобы мы могли иметь несколько ссылок на узел
|
||||||
|
// (например, одна ссылка у родителя, другая у курсора-итератора).
|
||||||
|
// RefCell нужен, чтобы мы могли менять children/keys внутри Rc.
|
||||||
|
children: Vec<Rc<RefCell<Node<T>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Node<T> {
|
||||||
|
fn new(keys: Vec<T>) -> Self {
|
||||||
|
Node {
|
||||||
|
keys,
|
||||||
|
children: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Создадим удобный алиас типа для "умной ссылки на узел"
|
||||||
|
type NodeLink<T> = Rc<RefCell<Node<T>>>;
|
||||||
|
|
||||||
|
// Вспомогательная функция для создания завернутого узла
|
||||||
|
fn make_node<T>(keys: Vec<T>) -> NodeLink<T> {
|
||||||
|
Rc::new(RefCell::new(Node::new(keys)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// --- СТРОИМ ДЕРЕВО (Ручная сборка для примера) ---
|
||||||
|
|
||||||
|
// Создаем листья
|
||||||
|
let leaf1 = make_node(vec![1, 2]);
|
||||||
|
let leaf2 = make_node(vec![4, 5]);
|
||||||
|
let leaf3 = make_node(vec![7, 8]);
|
||||||
|
|
||||||
|
// Создаем корень
|
||||||
|
let root = make_node(vec![3, 6]);
|
||||||
|
|
||||||
|
// Связываем корень с детьми.
|
||||||
|
// Нам нужно "залезть" внутрь Rc через borrow_mut(), чтобы изменить вектор детей.
|
||||||
|
{
|
||||||
|
let mut root_inner = root.borrow_mut();
|
||||||
|
|
||||||
|
// Rc::clone здесь просто увеличивает счетчик ссылок.
|
||||||
|
// leaf1 теперь принадлежит и переменной leaf1, и вектору внутри root.
|
||||||
|
root_inner.children.push(Rc::clone(&leaf1));
|
||||||
|
root_inner.children.push(Rc::clone(&leaf2));
|
||||||
|
root_inner.children.push(Rc::clone(&leaf3));
|
||||||
|
}
|
||||||
|
// Тут borrow_mut заканчивается, блокировка снимается.
|
||||||
|
|
||||||
|
// --- ИСПОЛЬЗОВАНИЕ ---
|
||||||
|
|
||||||
|
println!("Root strong count: {}", Rc::strong_count(&root)); // 1
|
||||||
|
println!("Leaf1 strong count: {}", Rc::strong_count(&leaf1)); // 2 (одна у нас в main, одна внутри root)
|
||||||
|
|
||||||
|
// Поиск значения 5
|
||||||
|
if search(&root, 5) {
|
||||||
|
println!("Found 5!");
|
||||||
|
} else {
|
||||||
|
println!("Not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пример рекурсивного поиска
|
||||||
|
fn search(node_link: &NodeLink<i32>, target: i32) -> bool {
|
||||||
|
// Берем ссылку на чтение через borrow()
|
||||||
|
let node = node_link.borrow();
|
||||||
|
|
||||||
|
// Проверяем ключи в текущем узле
|
||||||
|
for (i, &key) in node.keys.iter().enumerate() {
|
||||||
|
if key == target {
|
||||||
|
return true; // Нашли
|
||||||
|
}
|
||||||
|
if key > target {
|
||||||
|
// Если ключ больше искомого, нужно спускаться в ребенка (если он есть)
|
||||||
|
if node.children.len() > i {
|
||||||
|
return search(&node.children[i], target);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если дошли до конца ключей, проверяем последнего ребенка
|
||||||
|
if !node.children.is_empty() {
|
||||||
|
return search(node.children.last().unwrap(), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Почему именно такая структура?
|
||||||
|
|
||||||
|
1. **`Rc` вместо `Box`**: В строгом дереве обычно хватает `Box` (у каждого узла строго 1 родитель). Однако `Rc` часто используют, если:
|
||||||
|
* Мы хотим реализовать курсоры, которые указывают на узлы дерева независимо от корня.
|
||||||
|
* Мы хотим реализовать персистентную структуру данных (где новые версии дерева переиспользуют неизмененные поддеревья старых версий).
|
||||||
|
|
||||||
|
2. **`RefCell`**: Без него мы не смогли бы написать `root.children.push(...)`, так как `Rc` запрещает изменение содержимого. `RefCell` переносит проверку правил заимствования с этапа компиляции на этап выполнения (runtime).
|
||||||
|
|
||||||
|
### В чем подвох `Rc` в деревьях?
|
||||||
|
Если вы добавите ссылку "назад" (от ребенка к родителю) с помощью обычного `Rc`, вы создадите **Reference Cycle** (циклическую ссылку). Счетчики ссылок никогда не станут равны нулю, и память потечет (никогда не очистится).
|
||||||
|
Для ссылок "назад" к родителю нужно использовать `Weak<T>` (слабые ссылки), которые не увеличивают *strong_count*.
|
||||||
67
20-dev/00-rust/smart pointers/deref.md
Normal file
67
20-dev/00-rust/smart pointers/deref.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
## 1. Суть трейта `Deref`
|
||||||
|
|
||||||
|
Трейт `std::ops::Deref` позволяет кастомизировать поведение оператора разыменования `*`.
|
||||||
|
Главная цель — сделать **Smart Pointers** (умные указатели) взаимозаменяемыми с обычными ссылками в клиентском коде.
|
||||||
|
|
||||||
|
**Механизм работы:**
|
||||||
|
Когда вы пишете `*y` для типа, реализующего `Deref`, Rust неявно преобразует это в:
|
||||||
|
|
||||||
|
rust
|
||||||
|
|
||||||
|
`*(y.deref())`
|
||||||
|
|
||||||
|
1. Вызывается метод `deref()`, который возвращает **ссылку** на внутреннее значение (`&Target`).
|
||||||
|
|
||||||
|
2. Затем происходит обычное разыменование `*` этой полученной ссылки.
|
||||||
|
|
||||||
|
|
||||||
|
> **Важно:** Метод `deref` возвращает именно ссылку, а не значение, чтобы не перемещать (move) владение данными из умного указателя.
|
||||||
|
|
||||||
|
## 2. Реализация
|
||||||
|
|
||||||
|
Для реализации собственного умного указателя (как в примере `MyBox<T>`) нужно:
|
||||||
|
|
||||||
|
- Указать ассоциированный тип `Target` (тип данных внутри обертки).
|
||||||
|
|
||||||
|
- Реализовать метод `deref`.
|
||||||
|
|
||||||
|
|
||||||
|
rust
|
||||||
|
|
||||||
|
`use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 // Возвращаем ссылку на поле кортежной структуры } }`
|
||||||
|
|
||||||
|
## 3. Deref Coercion (Неявное приведение)
|
||||||
|
|
||||||
|
Это механизм эргономики Rust, который автоматически преобразует ссылки при передаче аргументов в функции или методы.
|
||||||
|
|
||||||
|
- **Как работает:** Если тип `T` реализует `Deref<Target=U>`, то `&T` может быть неявно приведен к `&U`.
|
||||||
|
|
||||||
|
- **Цепочки:** Coercion работает рекурсивно.
|
||||||
|
|
||||||
|
- _Пример:_ `&MyBox<String>` → `deref` → `&String` → `deref` → `&str`.
|
||||||
|
|
||||||
|
- Это позволяет передавать `&MyBox<String>` в функцию, ожидающую `&str`.
|
||||||
|
|
||||||
|
- **Цена:** Работает на этапе компиляции (compile-time resolution), **runtime оверхеда нет**.
|
||||||
|
|
||||||
|
|
||||||
|
Без этого механизма код превратился бы в нагромождение символов: `&(*m)[..]` вместо простого `&m`.
|
||||||
|
|
||||||
|
## 4. Взаимодействие с изменяемостью (Mutability)
|
||||||
|
|
||||||
|
Для изменяемых ссылок существует зеркальный трейт `DerefMut`.
|
||||||
|
|
||||||
|
**Три правила приведения (Coercion rules):**
|
||||||
|
Rust применяет приведение типов в следующих случаях:
|
||||||
|
|
||||||
|
1. **`&T` → `&U`**: Если реализован `Deref` (Immutable to Immutable).
|
||||||
|
|
||||||
|
2. **`&mut T` → `&mut U`**: Если реализован `DerefMut` (Mutable to Mutable).
|
||||||
|
|
||||||
|
3. **`&mut T` → `&U`**: Если реализован `Deref` (Mutable to Immutable).
|
||||||
|
|
||||||
|
|
||||||
|
> **Критическое ограничение:** Приведение **Immutable (`&T`) → Mutable (`&mut U`)** невозможно.
|
||||||
|
> Это нарушило бы правила заимствования (Borrowing Rules), так как нельзя гарантировать уникальность создаваемой изменяемой ссылки, если исходная ссылка была неизменяемой (а их может быть много).
|
||||||
|
|
||||||
|
4. [https://doc.rust-lang.org/book/ch15-02-deref.html](https://doc.rust-lang.org/book/ch15-02-deref.html)
|
||||||
60
20-dev/00-rust/smart pointers/drop.md
Normal file
60
20-dev/00-rust/smart pointers/drop.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
Вот краткая выжимка о трейте `Drop` на основе предоставленного материала.
|
||||||
|
|
||||||
|
### 1. Суть трейта `Drop`
|
||||||
|
Трейт `Drop` используется для настройки кода, который **автоматически** выполняется, когда значение выходит из области видимости (scope).
|
||||||
|
* **Основная цель:** Очистка ресурсов (освобождение памяти в куче, закрытие файлов, сетевых соединений, снятие блокировок).
|
||||||
|
* Это аналог **деструктора** в ООП языках.
|
||||||
|
* В Rust почти все умные указатели (например, `Box<T>`) используют `Drop` для корректного освобождения памяти.
|
||||||
|
|
||||||
|
### 2. Как реализовать
|
||||||
|
Нужно реализовать единственный метод `drop`, который принимает мутабельную ссылку на `self`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct CustomSmartPointer {
|
||||||
|
data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CustomSmartPointer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Ваш код очистки.
|
||||||
|
// В примере: просто выводим сообщение.
|
||||||
|
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Автоматический вызов:** Вам **не нужно** вызывать этот метод вручную. Rust сам вставит вызов в нужном месте (обычно в конце блока `{}`).
|
||||||
|
* **Порядок:** Переменные удаляются в порядке, **обратном** их созданию (LIFO - Last In, First Out).
|
||||||
|
|
||||||
|
### 3. Ручная очистка (Early Drop)
|
||||||
|
Иногда нужно освободить ресурс *раньше*, чем закончится область видимости (например, чтобы раньше снять блокировку `Mutex` или закрыть файл).
|
||||||
|
|
||||||
|
* **Проблема:** Rust **запрещает** вызывать метод `.drop()` вручную (ошибка компиляции `explicit destructor calls not allowed`), чтобы избежать двойной очистки (double free error).
|
||||||
|
* **Решение:** Используйте функцию `std::mem::drop(value)`. Она принимает значение по значению (забирает владение) и тут же выбрасывает его, вызывая деструктор.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let c = CustomSmartPointer { data: String::from("some data") };
|
||||||
|
println!("Created.");
|
||||||
|
|
||||||
|
// c.drop(); // ОШИБКА! Нельзя вызывать метод трейта напрямую.
|
||||||
|
|
||||||
|
drop(c); // Правильно. Явный вызов функции из std::mem.
|
||||||
|
// Деструктор отработает здесь.
|
||||||
|
|
||||||
|
println!("End of main.");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Итог одной строкой
|
||||||
|
`Drop` позволяет написать код, который выполнится автоматически при уничтожении объекта, а если нужно уничтожить объект досрочно — используйте функцию `std::mem::drop`.
|
||||||
|
|
||||||
|
[1](https://doc.rust-lang.ru/book/ch15-03-drop.html)
|
||||||
|
[2](https://doc.rust-lang.ru/stable/rust-by-example/trait/drop.html)
|
||||||
|
[3](https://labex.io/ru/tutorials/cleanup-with-rust-s-drop-trait-100433)
|
||||||
|
[4](https://habr.com/ru/articles/960608/)
|
||||||
|
[5](https://doc.rust-lang.ru/stable/rust-by-example/generics/gen_trait.html)
|
||||||
|
[6](https://www.reddit.com/r/rust/comments/uhz9mr/implementing_drop_manually_to_show_progress/)
|
||||||
|
[7](https://habr.com/ru/articles/277461/)
|
||||||
|
[8](https://www.youtube.com/watch?v=7ec7hpndex4)
|
||||||
|
[9](https://doc.rust-lang.org/std/ops/trait.Drop.html)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue